C++ For what types can the parameter type name be the same as the parameter name with a type specifier?
Asked Answered
G

4

5

This question is going to take a bit of explination, sorry. I am fixing an oversight in doxygen parsing some C++ code and I have come across an unusual corner case that doxygen doesn't account for. I have a fix but I want to make it more general so I need some explanation.

To illustrate the case where doxygen is failing I am going to define a contrived example involving The Simpsons (because that seems to be popular for these types of questions). Let's say that we have the following enum:

enum simpson { HOMER, MARGE, BART, LISA, MAGGIE };

Now we want to pass the enum value into a method (of the Simpsons class naturally) that looks like this:

const char* voicedBy(simpson simpson)
{
    switch (simpson) {
        case HOMER:
            return "Dan Castellaneta";
        case MARGE:
            return "Julie Kavner";
        case BART:
            return "Nancy Cartwright";
        case LISA:
            return "Yeardley Smith";
        case MAGGIE:
            return "*suck* *suck*";
    }
}

Unfortunately this produces a compiler error because the enum type 'simpson' is not allowed to be the same as the parameter name 'simpson' (Unlike in, say, C#). But, C++ has an answer to this. You put the enum keyword in front of the type name like this:

const char* voicedBy(enum simpson simpson)

and the code will now compile and run. Unfortunately doxygen doesn't account for this case and so it treats the entire string "enum simpson simpson" as a parameter type with no parameter name. I have come up with some code to fix doxygen in the case of an enum like above.

My question is, what other types is this kind of trick valid for? struct?, union?, typedef?, others? And for that matter, does the 'type specifier for method parameter with same name as parameter name' concept have a name so that I can get some more details on it?

Glyco answered 23/5, 2011 at 2:17 Comment(4)
Nice (well...nice in a "I didn't know that" sense, but not nice) trick.Christcrossrow
Please excuse me while I find whoever wrote that code and club them in the face :P.Habituate
What compiler and version? The above code sample should compile without any need for an elaborated type specifier.Transitive
@DavidRodríguez-dribeas gcc 2.95.3. A /seriously/ old version of gcc. You are right this kind of trick is not necessary in modern C++ compilers but the original code was not written with a modern compiler.Glyco
L
3

What compiler and version are you using?

void foo( simpson simpson ) {}

No enum present, that is, you don't need to use an elaborated type specifier in this context, and it compiles perfectly in gcc 4.2 and 4.6. The problem is that inside the function, the argument name hides* the type, and if you want to declare a new variable with that type inside that scope you will need the elaborated type specifier, but in the function signature it is parsed left to right, and that means that the first simpson is the enum and at that time there is no collision. The second simpson introduces a local name, and from there on, simpson refers to the parameter and not the type.

void relationship( /*enum*/ simpson simpson, enum simpson other = HOMER );
//                   ^^^^ optional           ^^^^ required
{
   enum simpson yet_another = simpson;
// ^^^^ required              ^^^^^^^ first argument!
}

The same type of name hiding can happen if you define a function with the same name as the type you want:

void simpson();
void voicedBy( enum simpson s );
//             ^^^^ required

Note that if you add a typedef the things change a little in this last case: there will be a name clash between the typedef-ed name and the function name (or a variable name in the same scope).

* Here hides is not used in the sense of a variable in one scope hiding a variable with the same name in an outer scope. In C++, as in C, there are two identifier spaces, one for user defined types, and another for mostly everything else including typedef-ed type aliases. Lookup in C++ is performed from the inner scope to the outer scope, and in each scope the global identifier space is searched, if the identifier is not found, then the user defined types identifier space is searched for the same identifier. This last step is not performed in C, where elaborated type specifiers are required always. Only if that also fails, the compiler will move to the next scope.

Lechery answered 23/5, 2011 at 7:43 Comment(1)
gcc 2.95.3. Finally tested this and you are correct, the enum part is not needed for method parameters in C++ code but is needed to declare a local variable of that type. I didn't check if enum is required for the parameter of a regular function as I don't use them.Glyco
E
3

In C, the canonical name of a struct, union, or enum includes that prefix:

struct Point {
    int x, y;
};

enum Type {
    FIRST, SECOND, THIRD
};

struct Point p;
enum Type t;

Which is the source of the idiom of creating a typedef name with the prefix removed:

typedef struct Point {
    int x, y;
} Point;

typedef enum Type {
    FIRST, SECOND, THIRD
} Type;

struct Point p;
enum Type t;
Point p;
Type t;

C++ has this as well, with the same behaviour also given to class, and analogous behaviour given to template and typename in templates. However, it also removes the requirement of including the prefix except in ambiguous cases.

I didn't think this concept had a name, but I stand corrected: it's an elaborated type specifier. A suitable workaround for this may be to place the Doxygen comments on the declaration rather than the definition.

Excurrent answered 23/5, 2011 at 2:28 Comment(1)
Your answer is good. Tony got the accepted answer only for knowing that this is called an 'elaborated type specifier'.Glyco
L
3

What compiler and version are you using?

void foo( simpson simpson ) {}

No enum present, that is, you don't need to use an elaborated type specifier in this context, and it compiles perfectly in gcc 4.2 and 4.6. The problem is that inside the function, the argument name hides* the type, and if you want to declare a new variable with that type inside that scope you will need the elaborated type specifier, but in the function signature it is parsed left to right, and that means that the first simpson is the enum and at that time there is no collision. The second simpson introduces a local name, and from there on, simpson refers to the parameter and not the type.

void relationship( /*enum*/ simpson simpson, enum simpson other = HOMER );
//                   ^^^^ optional           ^^^^ required
{
   enum simpson yet_another = simpson;
// ^^^^ required              ^^^^^^^ first argument!
}

The same type of name hiding can happen if you define a function with the same name as the type you want:

void simpson();
void voicedBy( enum simpson s );
//             ^^^^ required

Note that if you add a typedef the things change a little in this last case: there will be a name clash between the typedef-ed name and the function name (or a variable name in the same scope).

* Here hides is not used in the sense of a variable in one scope hiding a variable with the same name in an outer scope. In C++, as in C, there are two identifier spaces, one for user defined types, and another for mostly everything else including typedef-ed type aliases. Lookup in C++ is performed from the inner scope to the outer scope, and in each scope the global identifier space is searched, if the identifier is not found, then the user defined types identifier space is searched for the same identifier. This last step is not performed in C, where elaborated type specifiers are required always. Only if that also fails, the compiler will move to the next scope.

Lechery answered 23/5, 2011 at 7:43 Comment(1)
gcc 2.95.3. Finally tested this and you are correct, the enum part is not needed for method parameters in C++ code but is needed to declare a local variable of that type. I didn't check if enum is required for the parameter of a regular function as I don't use them.Glyco
H
1

struct/class/union also. In the Standard, "elaborated-type-specifier"s, consisting of "class-key identifier", see 3.4.4-1. (BTW - if a switch case returns, it has no need to break.)

Harbin answered 23/5, 2011 at 2:28 Comment(2)
I knew that I would screw something up in my example code since I didn't compile it.Glyco
@John: :-). Even that wouldn't have caught it - as is, will compile and work just fine... just those break lines can never be reached during execution - should be removed by the optimiser anyway, but the code will read better without.Harbin
T
1

What you did there is the same thing C coders do all day - prefixing their user defined types with the appropriate keyword. The same works for struct, class, union, in typedefs, variable declarations, anywhere basically.

Terisateriyaki answered 23/5, 2011 at 2:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.