1

g++我已经用和尝试了以下代码clang++foo两者都无法foo区分foo. 为什么会这样?C++ 标准是否规定了这一点?编译器不应该至少尝试两者吗?

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;
}

我可以用 替换::fooenum foo它会编译得很好。但是,如果我更改enum foo {...}typedef enum _foo {...} foo,同样的问题仍然存在(http://ideone.com/d1GiO)。

4

2 回答 2

4

在 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,structclass关键字,并且在允许非类型的上下文中),编译器将从最内层范围开始查找到外部范围,并针对每个范围它将首先在全局标识符空间中查找,如果在那里没有找到,那么它还将查找用户定义的类型。这就是使enum,structclass可选的原因。在大多数情况下,就是这样。

在您的特定情况下,您正在做一些可以使结果令人惊讶的事情。首先,您有一个声明,它使用相同的标识符来引用两个不同的事物。这很好,因为在到达函数名称之前,唯一调用的实体fooenum

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,进入,所以它会继续向外,直到找到命名空间级别的枚举。Barenum 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

但对于大多数其他目的,行为是一致的。我在答案正文中没有提到几个极端案例,但这远远超出了这个问题的范围。

于 2012-08-20T22:31:01.260 回答
1

除非明确限定范围,否则命名空间是由内而外的。所以你的static_cast<foo>(...)意思static_cast<first foo found>(...)。C 语言也是如此,因此可能是语言本身的一个属性。由于所有名称都是完全限定的,因此第一个命中foo是唯一重要的。

于 2012-08-20T20:37:59.330 回答