C/C++ Contributing Editors


Uncaught Exceptions: An Alan Smithee Film

Bobby Schmidt

Of mutability, volatility, portability, and other things that go bump in the night...


Copyright © 2001 Robert H. Schmidt

To const or Not to const

Q

C++ Standard subclause 8.3.5/3 makes clear that the following declares the same function twice:

void f(int x);

// same function,
// const is ignored
void f(const int x);

Obviously, then, it doesn’t matter what order you put the declarations in:

// still the same function
// declared twice
void f(const int x);
void f(int x);

But consider what happens when the second declaration is also a definition:

// case 1: x is declared non-const
// in f1’s declaration and const
// in its definition

void f1(int x);

void f1(const int x)
    {
    x = 1; // modify x
    }

// case 2: x is declared const
// in f2’s declaration and non-const
// in its definition

void f2(const int x);

void f2(int x)
    {
    x = 2; // modify x
    }

Most of my compilers reject case 1 and accept case 2, but one does the reverse. Which is correct, and why?

Thanks — Ray DiTutto

A

My reading of the same paragraph (8.3.5/3) suggests that case 2 is okay while case 1 is not. From that paragraph:

All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters...

The type of a function is determined using the following rules...

After producing the list of parameter types, several transformations take place upon these types to determine the function type.

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.

In case 1, the function f1 has two declarations:

void f1(int x)

void f1(const int x)

According to 8.5.3/3, the compiler ignores the const when determining f1’s type in each declaration. The compiler thus interprets the two declarations as

void f1(int x)

void f1(/*const*/ int x)

The two f1 declarations do not represent f1 overloads, but rather multiply declare the same f1 function.

As the subclause tells us, the const does affect the x parameter’s definition within the body of f1. Because x is const in that body, the statement x = 1 is in error.

The same analysis applies to case 2. There, the compiler considers f2 to be multiply declared as

void f2(/*const*/ int x)

void f2(int x)

In the context of f2’s body, the parameter x is (non-const) int, and the assignment x = 2 is allowed.

In general, a parameter serves two distinct roles, depending on context:

Although they don’t work in C++, I think the old K&R-style function definitions actually make the distinction more clear. In the C function definition

int f(a, b, c)
    int a;
    int b;
    int c;
    {
    /* ... */
    }

the parameter names appear twice, both as part of the function’s declarator, and as a list of local-scope objects declared just before the opening curly brace. This presentation reinforces the parameters’ dual purpose [1].

Mon Dieu! An Actual C Question

Q

Bobby,

My question is about Standard C, not C++.

Consider this code:

void f1(void *p);

void f2(volatile void *p)
    {
    f1(p);
    }

It compiles just fine with version 4.2 of my embedded compiler, Diab Data C. However, the newer version (4.3) issues the following error message:

'f1' : cannot convert parameter 1
from 'volatile void *' to 'void *'

I tried the code on two PC compilers (Visual C 5 and Borland C 5.5). Both produce warnings, but complete the compilation.

What behavior is “right” from the C Standard point of view?

Diab’s technical support person suggested I use the option flag -Xmismatch-warning to avoid my problem. But this option essentially eliminates type checking on pointers. With that flag on, the compiler allows the following code without errors:

void f3(short *ps)
    {
    long *pl = ps;
    }

typedef struct
    {
    int m1;
    } st1;

typedef struct
    {
    int m2;
    } st2;

void f4(st1 *p1)
    {
    st2 *p2 = p1;
    }

void f5(long l)
    {
    long *pl = l;
    }

I was amazed to find out that VC5 also compiles this code. BC5.5 compiles f3 and f4, but produces on error on f5.

For years I’ve thought that Standard C only allows three types of implicit pointer conversions (now I put this complex const/volatile issue aside):

Was I wrong ?

I know, there are two questions instead of one, but I found these questions closely related.

Regards — Michael Shatz

A

Your first code piece should not compile, as it involves an implicit conversion from void volatile * to void *. The conversion requires a reduction in qualification of the pointed-to type, which the C standard will not permit without a cast [2]. If you have trouble understanding this restriction, mentally replace the qualifier volatile with the qualifier const.

Your second code piece — the one with f’s 3 through 5 — also should not compile. In particular, you ought to see diagnostics for these three lines:

which involve three different non-standard implicit conversions:

On your final question: the C standard allows several kinds of pointer conversions [3]. Of those, the allowed implicit conversions are

where T is an incomplete or object type. Excepting the bits about qualification, this list matches your years-long belief.

Headbanger’s Ball

Q

I’ve been trying to find a way to make data members read/write within member functions, but read-only within non-member and friend functions, without having to write get functions — so far to no avail.

First I naively thought of a template class having a public conversion operator that returns const T&; but this requires that I first declare a const ref to the class, then initialize it by the conversion function (which then becomes redundant). This method doesn’t make private members visible anyway.

I next tried declaring two structs:

struct A
    {
    const int i;
    const float f;
    };

struct B
    {
    int i;
    float f;
    };

union C
    {
public:
    A a;
private:
    B b;
    };

But this forces me to write a.i or b.i when I want the public const or private non-const versions of the member. Furthermore, according to my compiler, unions can’t be inherited.

I also tried having a class with a public, nameless union; but again, that requires extra typing to select the union member.

Not giving up altogether, I tried to make a macro that would produce easy-to-type access functions for me:

#define member(type, name)\
    public:\
    const type &name() const\
        {\
        return name;\
        }\
    private:\
        type name

such that typing

member(int, i);
member(float, f);

would produce

public:
    const int &i() const
        {
        return i;
        }
private:
    int i;
public:
    const float &f() const
        {
        return f;
        }
private:
    float f;

Unfortunately one can’t use the same name for a variable and a function, so I’d have to make the macro name the functions _i and _f. This is much less satisfying than the original goal I’d set out to achieve.

Finally, I tried a macro that would create nameless unions within my class, such as,

union
    {
public:
    const int _i;
private:
    int i;
    };

but according to my compiler, nameless unions cannot have private members.

I’m not only frustrated by now, but even resentful; I can’t, for the life of me, justify the invisibility of private and protected data members mandated by the language.

In what way would read-only access by default be a violation of encapsulation? Quite to the contrary — it seems to me that invisibility causes the biggest violation of encapsulation of all: programmers making data members public to avoid having to write get functions! I often have to work with other people’s code, and much of my time is spent writing the get functions they couldn’t bother writing themselves.

Any help would be enormously appreciated.

Thanks in advance — Dan Watkins

A

While I would normally whittle down such a long question, I want to show others how pursuit of a seemingly simple goal can lead to so many dead ends — and how this language can drive anyone to the brink of despair.

If I’m reading your intent correctly, you’re looking to allow

class X
    {
public:
    void f()
        {
        i = 6; // OK
        }
    int i;
    };

int main()
    {
    X x;
    x.i = 6; // error
    }

In effect, you want i to behave as non-const within X, and const outside X.

I’m sorry to say that C++ offers no direct way to get what you want. About the closest I can easily come is

class X
    {
public:
    X() : i_(0), i(i_)
        {
        }
    void f()
        {
        i;      // OK
        i = 6;  // error
        i_ = 6; // OK
        }
    int const &i;
private:
    int i_;
    };

int main()
    {
    X x;
    x.i;      // OK
    x.i = 6;  // error
    x.i_ = 6; // error
    }

Compared to your ideal, this method suffers one deficiency: the member function f must reference the data member as i_ instead of i for writeable access. Otherwise, this solution has the notational convenience and selective protection you want.

Erratica

Erratum the first: my October 2000 column item Ambiguity Rides Again contains a bug. The statement

B2 b2 = val;

won’t compile with the definition of B provided. That statement is a remnant from an earlier code version I was testing, and shouldn’t be there.

Erratum the second: several Diligent Readers question my solution for the December 2000 item Nested Functions. In that item, I explore several possible answers to Mike Retondo’s questions about functors. One of those answers amounts to

typedef void (*callback)();

void test(callback f)
    {
    f();
    }

struct X
    {
    operator callback();
    };

int main()
    {
    X x;
    test(x);
    return 0;
    }

x converts itself to test’s parameter type (callback)) through a conversion operator (X::operator callback). In effect, the object x acts as if it were really a function taking no parameters and returning void.

I never gave the implementation for the conversion operator. In that vacuum, some readers assumed that I’d mimic Mike’s original solution via

struct X
    {
    operator callback()
        {
        printf("test");
        }
    };

This isn’t the solution I had in mind, for two reasons:

Other readers thought I really meant

struct X
    {
    operator callback()
        {
        printf("test");
        return operator callback;
        }
    };

which won’t compile, since a function can’t return itself [4].

What I actually had in mind is

struct X
    {
    operator callback()
        {
        return f;
        }
private:
    static void f()
        {
        printf("test");
        }
    };

which returns a proper function pointer, and traces the call through that pointer. (A couple of readers recommend essentially this same implementation.)

Such a solution allows the same behavior for all X objects. A more sophisticated solution allows distinct per-object behavior:

struct X
    {
    operator callback()
        {
        instance_ = this;
        return f;
        }
private:
    static void f()
        {
        printf("%p", (void *) instance_);
        }
    static X const *instance_;
    };

at the cost of a static data member and a restriction on X object usage: because the same instance_ is shared by all X objects, only one X-to-callback conversion can be in play at a time.

Notes

[1] Not that I’m advocating this style, mind you. For pedagogical purposes, I think it has some merit; but overall, I recommend you favor prototype-style function declarators instead.

[2] C99 standard subclause 6.5.4/3.

[3] The whole retinue appears in subclause 6.3.2.3 of the C99 Standard.

[4] I explore the problem of self-returning functions in my July 1999 column item Chicken and Egg and October 1999 followup Counting Chickens.

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.