C/C++ Contributing Editors


Uncaught Exceptions: No No .NET

Bobby Schmidt

Bobby does a little moonlighting at the VC++ help desk, and battles some phantom foos in his spare time.


Copyright © 2000 Robert H. Schmidt

I keep getting letters commenting that my various and sundry lengthof solutions such as

template<typename T, size_t N>
size_t lengthof(T (&)[N])
    {
    return N;
    }

won't compile with Visual C++ v6. I know I've mentioned this before, but I apparently can't mention it enough: these examples do not work with Visual C++ v6! That compiler does not properly support the non-type template parameters lengthof requires.

In my October column, I promised to discover if lengthof will work in Visual C++ v7, which we have rebranded Visual C++ .NET. From what I can tell, the answer is no, at least in recent internal builds.

On a related note: as I write this, a prerelease of Visual C++ is now available for free download as part of the .NET Framework SDK [1]. I expect that by the time this column hits print, many of you will have tried the .NET compiler.

Even so, when I write "Visual C++" in my column, I will continue to mean the released compiler Visual C++ v6. Once Visual C++ .NET is released [2], I will start specifying which version I mean. After Visual C++ .NET is out for a while, I'll make that version my default.

The Phantom const

Q

Bobby,

Can you please look at the enclosed small examples, and tell me why the first compiles while the second does not? Sorry to say, I'm using Visual C++, which I know is your favorite compiler to be questioned on!

Regards — Jan Pringle

A

I've rewritten your first example as

void f(int /*const*/ n);
void f(int   const   n);

int main()
    {
    f(1);
    return 0;
    }

I'm declaring the same function f twice — first with a const parameter, then with a non-const parameter. Note that these are not f overloads, but rather are multiple declarations of the same f. Because only one f is available — multiply declared though it may be — the compiler finds no ambiguity when resolving the call f(1).

I am thus able to declare the same function multiple times with different parameter lists. Such behavior is supported by the C++ Standard. From subclause 8.3.5/3, in reference to function declarations:

Any cv-qualifier modifying a parameter type is deleted. [Example: the type void(*)(const int) becomes void(*)(int)end example] Such cv-qualifiers affect only the definition of the parameter within the body of the function; they do not affect the function type.

The compiler treats the second f declaration as if I'd written it exactly like the first, sans const. The const does not affect the signature or type of f.

Why does the Standard allow this behavior? Because the constness of n does not change the external semantics of f — the fact that n may be const is f's implementation detail. From the caller's perspective, n is a local object within f. Any changes to that local n do not affect the argument passed to f.

As written, this program builds with all my test systems (including Visual C++). But as you found in your second example, the variation

void f(int /*const*/ n);
void f(int   const   n);

typedef void (*fp) (int);
typedef void (*fpc)(int const);

fp  p  = f; // OK
fpc pc = f; // should be OK?

int main()
    {
    f(1);
    p(1);
    pc(1);
    return 0;
    }

fails with Visual C++ (although it does work with my other systems). The compiler flags the definition of pc with the incomplete diagnostic

cannot convert '' to
'void (*)(const int)'

None of the functions with this 
name in scope match the target type

(I just love error messages that reference type '' — so clean, so concise!)

Is Visual C++ in error here? I think so, but my interpretation is based on a somewhat liberal reading of 8.3.5/3.

The subclause covers declarations of functions, not declarations of pointers to functions. The subclause also discusses adjustment to parameters of those declared functions and gives a specific example of one such adjustment:

[Example: the type void(*)(const int) becomes void (*)(int)end example]

I'm not certain whether this adjustment involves a parameter of type const int or a parameter of type void(*)(const int). But I feel the intent is clear — the two function pointer types are synonymous. I'll therefore go out on a small limb and assume that my second example should compile.

On a hunch, I reversed the order of the two f declarations and then recompiled. The resulting Visual C++ error message changed to

cannot convert '' to 'void (*)(int)'

None of the functions with this
name in scope match the target type

This behavior implies that the compiler remembers the first f declaration it sees and then measures type compatibility literally against that first declaration. What I find interesting is that this literal comparison apparently affects only function pointer types, and not the function types themselves.

I also find interesting that Visual C++ maps p and pc to the decorated names ?p@@3P6AXH@ZA and ?pc@@3P6AXH@ZA. That the names contain the same encoded type information implies that p and pc literally share the same type. This evidence strongly suggests that the type-matching error is simply a bug rather than "by design."

The Phantom this

Q

Bobby,

The enclosed program works fine, giving me the output

CFoo::Func1

Is this legal C++?

Thanks — Sandeep

A

(A variation of your program appears as Listing 1.)

Your program is "legal" in that the Standard does not forbid it. The program also yields undefined behavior. From subclause 9.3.1:

If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

*pFoo is not an object of type CFoo. Indeed, it's not an object of any type, since its lifetime has not yet started. From subclause 3.8:

The lifetime of an object of type T begins when:

— storage with the proper alignment and size for type T is obtained, and

— if T is a class type with a non-trivial constructor, the constructor call has completed.

The properties ascribed to objects throughout this International Standard apply for a given object during its lifetime. [Note: in particular, before the lifetime of an object starts... there are significant restrictions on the use of the object, as described below...]

The "significant restrictions" involve proto-objects that have storage but are not yet fully constructed. Since *pFoo has no storage, it does not enjoy even this restricted use as an object.

Given that the program engages in undefined behavior, why does it apparently run with no ill effect? Probably because Func1 doesn't reference the non-existent object *pFoo. If you change Func1 to reference *pFoo:

void Func1()
    {
    i = 1; // really (*pFoo).i = 1
    }

you may well see different results. On my test systems, the modified program crashes when run.

Nested Functions

Q

I remember reading somewhere that you can use a functor (function object) as a callback function and simulate a Pascal nested function. I tried writing a test application but can't get it to compile under VC++ v6. With my enclosed code I get this error:

'TestCallback' :
cannot convert parameter 1 from 'struct main::CFoo'
to 'void (*)(void)'

No user-defined-conversion operator
available that can perform this conversion,
or the operator cannot be called

Thanks — Mike Retondo

A

Your code appears as Listing 2.

You're on the right track with CFoo::operator(), since that allows the expression

aFoo(); // same as aFoo.operator()();

just as if aFoo really were a function.

However, your attempt to call

TestCallback(aFoo);

requires a conversion from the aFoo argument to TestCallback's function pointer parameter. Real functions support this conversion implicitly; CFoo objects do not. As your compiler's error message suggests, the conversion must manifest as an explicit conversion operator:

struct CFoo
    {
    operator CallbackFuncPtr();
    // ...
    };

If you add this member, you'll find that your code builds and runs as you expect.

While this solution works, it's not very adaptable. If you later add

typedef int (*CallbackFuncPtr2)();

void TestCallback2 (CallbackFuncPtr2 pFunc);

you'll need to retrofit a second conversion operator:

struct CFoo
    {
    operator CallbackFuncPtr();
    operator CallbackFuncPtr2();
    // ...
    };

CFoo aFoo;
TestCallback(aFoo);  // OK
TestCallback2(aFoo); // OK

A more adaptable solution is to make TestCallback a function template:

template<typename Pred>
void TestCallback(Pred p)
    {
    p();
    }

This template works for any type Pred that supports the () operator. The technique mimics STL algorithms (such as find_if and for_each) that accept predicates. While those predicates can be functions or function pointers, they are often implemented by functors.

Unfortunately for you, the original CFoo will not work with templates. That class is declared local to a function (main) — and such local classes cannot be used as function template arguments (subclause 14.3.1). For CFoo to work as a template argument, you'd have to move it outside main, thereby defeating your desire to mimic local functions.

Round and Round Again

Q

Hey Bobby,

In the July issue of CUJ, you answer the question about initializing a variable with itself. One serious flaw with the language that allows exploitation of a class that you didn't touch upon is this failing in the language specification. Consider the following class definition:

class CFoo
    {
public:
    CFoo(CSomeComplexClassType const x);
    };

This CFoo class cannot be constructed without first constructing CSomeComplexClassType, right? So we would like to believe. By the above fiendish deception of the compiler, the following declaration is valid:

CFoo myFoo = myFoo;

While the results of this may be undetermined, it is portably indeterminant. All of the compilers that I've tried have allowed this construct, because, as you said, the Standard clearly does not prohibit it. Anyone out there not quaking from the ramifications of such a simple line of code obviously doesn't realize the magnitude of the breach. Essentially, any complex class may be constructed with invalid data.

This is particularly insidious when considering mathematical primitives that contain only atomic types and have rigidly defined interfaces to prevent mishandling, but are terribly vulnerable to this sort of abuse. I stumbled upon this quirk by accident and tracked it down to some uninitialized values in a class without a default constructor. Stumped, I eventually had someone else look at the obvious assignment line (where I'd mixed up two similar variable names) and found the bug.

Since then, I've been careful to not tell too many people about this, for fear it might become a practice of the reckless. Shame on you for publishing it! <grin> — Jason Hughes

A

When I wrote up that column entry, I wasn't making an editorial on whether you should write

CFoo myFoo = myFoo;

or not. I was only validating that yes, the Standard allows this, and no, the Standard probably shouldn't try to make a special case of this one invalid initialization. I left it for readers to decide if this was a useful program form.

But as they say in the NFL, upon further review, I've decided the referee should have flagged the play as out-of-bounds.

The statement

CFoo myFoo = myFoo;

runs because CFoo has an accessible copy constructor. In the example you describe, where you've overtly given the class one (non-copy) constructor:

class CFoo
    {
public:
    CFoo(CSomeComplexClassType
        const x);
    };

CFoo still has an implicitly-declared copy constructor. So one easy way to avoid our dilemma is to hide the copy constructor:

class CFoo
    {
public:
    CFoo(CSomeComplexClassType
        const x);
private:
    CFoo(CFoo const &);
    };

CFoo myFoo = myFoo; // error, at last!

This works, but at a price. Copy construction is a quite useful feature for a category; removing that feature to prevent one narrow class of problem seems extreme.

I can offer an alternative: apply the canonical self-assignment guard we normally use in operator=. I show one possible implementation in Listing 3.

Note to Diligent Readers: After this month, I'm declaring a moratorium on CFoos, foos, bars, foobars, FooBars, DoveBars, and other related mutations. After all these decades of programming, surely we can be more creative than this!

Notes

[1] http://msdn.microsoft.com/code/sample.asp?url=/msdn-files/027/000/976/msdncompositedoc.xml

[2] To stave off email: no, I can't say when that release will be.

Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also an alumnus of Microsoft, a speaker at the Software Development and Embedded Systems Conferences, and an original "associate" of (Dan) Saks & Associates. In other career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.