简体   繁体   English

当用作模板参数时,C ++编译器不区分类型和函数名称

[英]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. 当在全局标识符空间中查找标识符时(即,没有前面的enumstructclass关键字,并且在允许使用非类型的上下文中),编译器将从最里面的范围开始查找到最外面的范围,并且对于每个作用域,它将首先在全局标识符空间中查找,如果在那里找不到任何内容,则还将在用户定义的类型中查找。 This is what makes the enum , struct or class optional. 这就是使enumstructclass可选的原因。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM