在 C 和 C++ 中(这是继承的)有两个单独的标识符空间:一个用于通用符号,包括变量、函数……另一个用于用户定义的类型(枚举、结构)。表达式为通用标识符空间内的typedef
用户定义类型创建别名。
因为标识符空间是分开的,所以即使在相同的上下文中,该语言也允许您定义同名的函数和类型:
enum foo { no, yes };
void foo() {}
在 C 中,您被迫明确限定用户定义类型空间中的标识符,因此在前面的示例中,要使用foo
您必须执行的类型enum foo
:
void foo( enum foo ) {}
现在, atypedef
在全局标识符空间中创建了一个别名,因此通过使用 typedef,您无需限定即可逃脱:
typedef enum bar { no, yes } bar;
void foo( bar ) {} // uses the typedef
及时向 C++ [*]前进,两个标识符空间仍然在语言中,改变的是查找规则。在全局标识符空间中查找标识符时(即没有前面的enum
,struct
或class
关键字,并且在允许非类型的上下文中),编译器将从最内层范围开始查找到外部范围,并针对每个范围它将首先在全局标识符空间中查找,如果在那里没有找到,那么它还将查找用户定义的类型。这就是使enum
,struct
或class
可选的原因。在大多数情况下,就是这样。
在您的特定情况下,您正在做一些可以使结果令人惊讶的事情。首先,您有一个声明,它使用相同的标识符来引用两个不同的事物。这很好,因为在到达函数名称之前,唯一调用的实体foo
是enum
:
foo // No need for 'enum', at this point function 'foo' is not declared
foo(); // No collision, this is 'foo' in global id space, not 'enum foo'
现在,在里面Bar::foo
,如果你单独使用foo
它,它将开始在函数的范围内查找,在那里它不会找到任何东西。然后它将移动到Bar
类范围,在那里它会看到有一个foo
函数并且查找将在那里停止。请注意,无论您是否具有枚举器的 typedef 都没有区别,因为查找在离开Bar
类之前停止,因此在可能在命名空间级别找到任一标识符之前停止。
正如您所指出的,如果您添加enum
关键字,那么您会突然更改规则。您现在正在寻找用户定义类型,搜索将遵循相同的范围,但只会在标识符空间中查找用户定义类型。所以它会在没有的地方离开Bar::foo
,进入,所以它会继续向外,直到找到命名空间级别的枚举。Bar
enum foo
如果您提供命名空间限定,则会发生同样的事情:在全局命名空间级别foo
开始之前::
需要查找。在该级别中,唯一foo
定义的是用户定义类型enum foo
。请注意,这与添加或不添加enum
关键字是正交的。在原始代码中,您可以foo
在命名空间级别拥有一个函数,这会导致类似的问题:
enum foo { no, yes };
void foo() {}
class Bar {
foo foo() { // Error [1]
return static_cast<::foo>(0); // Error [2]
}
};
在这个例子中,有两个错误。声明返回类型的时候,函数Bar::foo
还没有声明,但是有::foo()
,按照上面的规则,lookup 会向外直到全局命名空间,find::foo()
在检查之前enum foo
。第二个错误,基本相同,是static_cast
请求全局命名空间中的限定在此处无济于事。原因是查找会在找到::foo()
之前再次找到enum foo
。正确的代码是:
class Bar {
enum foo foo() {
return static_cast<enum ::foo>(0); // :: is optional!
}
};
在这种情况下,命名空间限定再次是可选的,但这只是因为没有foo
用户定义的类型可以通过查找找到。但是我们可以让代码更复杂一些......
enum foo { no, yes };
void foo() {}
class Bar {
struct foo {};
enum ::foo foo() {
return static_cast<enum ::foo>(0);
}
};
没有::
限定条件,Bar::foo()
声明和static_cast
都将失败,因为在找到之前有一个用户定义的类型Bar::foo
将被命中enum ::foo
。如果没有enum
,代码也将无法编译,因为全局函数::foo()
将在enum
.
总结一下,经过长时间的解释:不要。避免创建与类型同名的函数,因为这很可能只会引起混淆。
[*]重读标准后,C++ 语言没有为用户定义类型定义单独的标识符空间。另一方面,标准中的规则与上述描述一致。主要区别在于,在 C++ 中,typedef
别名不能与任何现有类型相同,而不是它所使用的类型:
struct foo {};
typedef int foo; // Error
但对于大多数其他目的,行为是一致的。我在答案正文中没有提到几个极端案例,但这远远超出了这个问题的范围。