[英]C++ compiler does not distinguish type and function name when used as template parameter
I have tried the following code both with g++
and clang++
. 我已经尝试使用g++
和clang++
编写以下代码。 Both cannot distinguish the type foo
and the function name foo
inside the body of foo
. 既不能区分类型foo
和函数名foo
的体内foo
。 Why is this the case? 为什么会这样呢? Does the C++ standard mandates this? C ++标准是否要求这样做? 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 ::foo
with enum foo
, and it will compile just fine. 我可以用enum foo
替换::foo
,它将编译得很好。 However, if I change enum foo {...}
to typedef enum _foo {...} foo
, the same problem remains ( http://ideone.com/d1GiO ). 但是,如果我将enum foo {...}
更改为typedef enum _foo {...} foo
,则仍然存在相同的问题( 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). 在C和C ++(继承)中,有两个单独的标识符空间:一个用于通用符号,包括变量,函数...,另一个用于用户定义类型(枚举,结构)。 A typedef
expression creates an alias to a user defined type inside the general identifier space. typedef
表达式在常规标识符空间内为用户定义的类型创建别名。
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: 由于标识符空间是分开的,因此即使在相同的上下文中,该语言也允许您定义具有相同名称的函数和类型:
enum foo { no, yes };
void foo() {}
In C you were forced to qualify identifiers in the user defined types space explicitly, so in the previous example, to use the foo
type you would have to do enum foo
: 在C语言中,您被迫显式限定用户定义类型空间中的标识符,因此在上一个示例中,要使用foo
类型,您必须执行enum foo
:
void foo( enum foo ) {}
Now, a typedef
creates an alias in the global identifier space, so by using a typedef you can get away without qualification: 现在,一个typedef
在全局标识符空间中创建一个别名,因此,使用typedef可以无限制地逃脱:
typedef enum bar { no, yes } bar;
void foo( bar ) {} // uses the typedef
Moving forward in time to C++ [*] , both identifier spaces are still in the language, what has changed are the lookup rules. 随着时间的推移,向C ++ [*]前进,两个标识符空间仍在该语言中,已更改的是查找规则。 When looking for an identifier in the global identifier space (ie without a preceding enum
, struct
or class
keyword, 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. 当在全局标识符空间中查找标识符时(即,没有前面的enum
, struct
或class
关键字,并且在允许使用非类型的上下文中),编译器将从最里面的范围开始查找到最外面的范围,并且对于每个作用域,它将首先在全局标识符空间中查找,如果在那里找不到任何内容,则还将在用户定义的类型中查找。 This is what makes the enum
, struct
or class
optional. 这就是使enum
, struct
或class
可选的原因。 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 foo
is the enum
: 很好,因为在到达函数名称之前,唯一的名为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'
Now, inside Bar::foo
, if you use foo
by itself it will start looking in the scope of the function, where it will not find anything. 现在,在Bar::foo
内部,如果Bar::foo
使用foo
,它将开始在函数范围内查找,在此范围内什么也找不到。 It will then move out to the Bar
class scope, where it will see that there is a foo
function and lookup will stop there. 然后它将移至Bar
类范围,在该范围中将看到存在foo
函数,并且查找将在那里停止。 Note that whether you have the typedef or not for the enumerator does not make a difference, as lookup stops before leaving the Bar
class, and thus before potentially finding either identifier at namespace level. 请注意,是否为枚举器使用typedef都没有区别,因为查找在离开Bar
类之前停止,因此在可能在名称空间级别找到任何一个标识符之前都会停止。
As you noted, if you add the enum
keyword, then suddenly you change the rules. 如前所述,如果添加enum
关键字,则突然会更改规则。 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 of Bar::foo
, into Bar
, where there is no enum foo
, so it will continue outwards until it finds the enumeration at namespace level. 因此它将从Bar::foo
进入Bar
,那里没有enum foo
,因此它将继续向外运行,直到在命名空间级别找到该枚举为止。
If you provide namespace qualification, the same thing happens: prepending foo
with ::
requires lookup to start at the global namespace level. 如果提供名称空间限定,则会发生相同的事情:在foo
前面加上::
要求从全局名称空间级别开始查找。 In that level, the only foo
that is defined is the user defined type enum foo
. 在该级别,唯一定义的foo
是用户定义的类型enum foo
。 Note that this is orthogonal to the adding or not the enum
keyword. 请注意,这与添加或不包含enum
关键字正交。 In the original code, you could have a function foo
at namespace level, and that would cause a similar issue: 在原始代码中,您可能在名称空间级别拥有一个函数foo
,这将导致类似的问题:
enum foo { no, yes };
void foo() {}
class Bar {
foo foo() { // Error [1]
return static_cast<::foo>(0); // Error [2]
}
};
In this example there are two errors. 在这个例子中有两个错误。 When declaring the return type, the function Bar::foo
is 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 for enum foo
. 声明返回类型时,尚未声明函数Bar::foo
,但是存在::foo()
,按照上述规则,查找将向外进行,直到全局名称空间并在检查enum foo
之前找到::foo()
。 enum foo
。 The second error, which is the basically the same, is that the qualification in the static_cast
requesting the global namespace will not help here. 第二个错误,基本上是相同的,在于static_cast
请求全局名称空间的限定在这里无济于事。 The reason is that lookup will again find ::foo()
before it finds enum foo
. 原因是查找将在找到enum foo
之前再次找到::foo()
。 The correct code for this would be: 正确的代码是:
class Bar {
enum foo foo() {
return static_cast<enum ::foo>(0); // :: is optional!
}
};
In this case, the namespace qualification is again optional, but only because there is no foo
user defined type that will be found by lookup. 在这种情况下,名称空间限定还是可选的,但这仅是因为没有foo
用户定义的类型可以通过查找找到。 But we can make the code a bit more convoluted... 但是我们可以使代码更加复杂...
enum foo { no, yes };
void foo() {}
class Bar {
struct foo {};
enum ::foo foo() {
return static_cast<enum ::foo>(0);
}
};
Without the ::
qualification, both the Bar::foo()
declaration and the static_cast
would fail because there is a user defined type Bar::foo
that will be hit before the enum ::foo
is found. 没有::
, Bar::foo()
声明和static_cast
都将失败,因为存在用户定义的Bar::foo
类型,该类型将在找到enum ::foo
之前被击中。 Without the enum
, the code will also fail to compile, as the global function ::foo()
will be considered before the enum
. 没有enum
,代码也将无法编译,因为全局函数::foo()
将在enum
之前被考虑。
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. [*]重新阅读标准后,C ++语言未为用户定义的类型定义单独的标识符空间。 On the other hand, the rules in the standard are consistent with the description above. 另一方面,该标准中的规则与上述描述一致。 The main difference is that in C++ a typedef
alias cannot be the same as any existing type other than the type it is aliasing: 主要区别在于,在C ++中, typedef
别名不能与任何现有类型相同,除了要作为别名的类型之外:
struct foo {};
typedef int foo; // Error
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. 我在答案正文中没有提到几个极端的情况,但是这超出了此问题的范围。
Namespacing is in to out, unless explicitly scoped. 命名空间是由内到外的,除非有明确的作用域。 So your static_cast<foo>(...)
means static_cast<first foo found>(...)
. 因此您的static_cast<foo>(...)
表示static_cast<first foo found>(...)
。 This is also the case with C, so is probably a property of the language itself. C语言也是如此,这可能是语言本身的属性。 Since all names are fully qualified, the first hit on foo
is the only one that matters. 由于所有名称都是完全合格的,因此对foo
的第一个命中是唯一重要的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.