I have tried the following code both with g++ and clang++. Both cannot distinguish the type foo and the function name foo inside the body of foo. Why is this the case? Does the C++ standard mandates this? Shouldn’t the compiler at least try both?
enum foo {
FOO = 0,
BAR,
BAZ
};
class Bar {
public:
foo foo () const
{
// does not compile if I write static_cast<foo>(...)
return static_cast< ::foo>(m_bar);
}
int m_bar;
};
int main ()
{
Bar bar;
bar.m_bar = 0;
foo foo_bar = bar.foo();
return 0;
}
I can replace ::foowith enum foo, and it will compile just fine. However, if I change enum foo {...} to typedef enum _foo {...} foo, the same problem remains (http://ideone.com/d1GiO).
In C and in C++ (this was inherited) there are two separate identifier spaces: one for general symbols, including variables, functions… and a separate one for user defined types (enums, structs). A
typedefexpression creates an alias to a user defined type inside the general identifier space.Because the identifier spaces are kept separate, the language allows you to define a function and a type of the same name even in the same context:
In C you were forced to qualify identifiers in the user defined types space explicitly, so in the previous example, to use the
footype you would have to doenum foo:Now, a
typedefcreates an alias in the global identifier space, so by using a typedef you can get away without qualification:Moving forward in time to C++[*], both identifier spaces are still in the language, what has changed are the lookup rules. When looking for an identifier in the global identifier space (i.e. without a preceding
enum,structorclasskeyword, and in a context where non-types are allowed), the compiler will start lookup from the inner most scope to the outer scope, and for each scope it will lookup first in the global identifier space, and if nothing is found there, then it will also look in the user defined types. This is what makes theenum,structorclassoptional. In most cases, that is.In your particular case, you are doing a couple of things that can make the outcome surprising. First, you have a declaration that uses the same identifier to refer to two different things. This is fine, because until the name of the function is reached, the only entity called
foois theenum:Now, inside
Bar::foo, if you usefooby itself it will start looking in the scope of the function, where it will not find anything. It will then move out to theBarclass scope, where it will see that there is afoofunction and lookup will stop there. Note that whether you have the typedef or not for the enumerator does not make a difference, as lookup stops before leaving theBarclass, and thus before potentially finding either identifier at namespace level.As you noted, if you add the
enumkeyword, then suddenly you change the rules. You are now looking for a user defined type, and the search will follow the same scopes, but will only look in the identifier space for user defined types. So it will go out ofBar::foo, intoBar, where there is noenum foo, so it will continue outwards until it finds the enumeration at namespace level.If you provide namespace qualification, the same thing happens: prepending
foowith::requires lookup to start at the global namespace level. In that level, the onlyfoothat is defined is the user defined typeenum foo. Note that this is orthogonal to the adding or not theenumkeyword. In the original code, you could have a functionfooat namespace level, and that would cause a similar issue:In this example there are two errors. When declaring the return type, the function
Bar::foois not yet declared, but there is::foo(), and following the rules above lookup will go outwards until the global namespace and find::foo()before checking forenum foo. The second error, which is the basically the same, is that the qualification in thestatic_castrequesting the global namespace will not help here. The reason is that lookup will again find::foo()before it findsenum foo. The correct code for this would be:In this case, the namespace qualification is again optional, but only because there is no
foouser defined type that will be found by lookup. But we can make the code a bit more convoluted…Without the
::qualification, both theBar::foo()declaration and thestatic_castwould fail because there is a user defined typeBar::foothat will be hit before theenum ::foois found. Without theenum, the code will also fail to compile, as the global function::foo()will be considered before theenum.Summing up, and after a long explanation: don’t. Avoid creating functions that have the same name that types, as that will most probably just cause confusion.
[*] After rereading the standard, the C++ language does not define separate identifier spaces for user defined types. On the other hand, the rules in the standard are consistent with the description above. The main difference is that in C++ a
typedefalias cannot be the same as any existing type other than the type it is aliasing:But for most other purposes, the behavior is consistent. There are a couple of corner cases that I did not mention in the body of the answer, but that are well beyond the scope of this question.