为什么不能在 C++ 中重新定义 class 中的类型名称?

Why can't redefine type names in class in C++?

根据 C++ Primer 部分,7.4.1 Type Names Are Special:

Ordinarily, an inner scope can redefine a name from an outer scope even if that name has already been used in the inner scope. However, in a class, if a member uses a name from an outer scope and that name is a type, then the class may not subsequently redefine that name.

据此,例如:

typedef double Money;
class Account {
    public:
        Money balance() { return bal; }
    private:
        typedef double Money;
        Money bal;
};

int main() {
    typedef double Money;
    Money asset;
    typedef double Money;
    return 0;
}

当你编译上面的例子时,它会报错:

a.cc:6:24: error: declaration of ‘typedef double Account::Money’ [-fpermissive]
         typedef double Money;
                        ^
a.cc:1:16: error: changes meaning of ‘Money’ from ‘typedef double Money’ [-fpermissive]
 typedef double Money;

那么为什么我们不能在 class 中重新定义类型名称,但我们可以在内部范围内重新定义类型名称吗?


我的编译器版本是g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
该部分还有一条注释:

Although it is an error to redefine a type name, compilers are not required to diagnose this error. Some compilers will quietly accept such code, even though the program is in error.

当编译器读取行时

    Money balance() { return bal; }

在class的定义中,已经在class之外使用了Money的定义。这使得行

    typedef double Money;

里面class一个问题。但是,在 class 中使用之前,您可以在 class 中使用重新定义 Money。以下就OK了

typedef double Money;

class Account {
    public:
        typedef double Money;
        Money balance() { return bal; }
    private:
        Money bal;
};

引用中的重点是:

hence the class may not subsequently redefine that name.

这不是类型所独有的。 [basic.class.scope]/2:

A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.

原因是 class 范围内的名称查找有点特殊。考虑:

using Foo = int;

struct X {
    Foo a;    // ::Foo, i.e., int
    void meow() { 
        Foo b = a; // X::Foo; error: no conversion from int to char*
    }
    using Foo = char*;
};

成员函数体中的名称查找考虑所有 class 成员,无论是在成员函数之前还是之后声明(否则,在 class 定义中定义的成员函数将无法使用稍后在 class 中声明的数据成员)。结果是您得到两个具有不同含义的 Foo,即使它们在词法上都位于 class 成员 Foo 的声明之前。这很容易导致极其混乱和脆弱的代码,因此标准禁止它。

我想尝试回答您评论中的一些问题。

评论 1

"But in the function main, we can redefine typedef double Money even if it is defined after the statement Money asset"

所以你在问,为什么 typedef 标识符可以在非成员函数中定义多次(在非 class 范围内)?

答案在这里:Repeated typedefs - invalid in C but valid in C++?

评论2

So, in this example, two Foos with different meaning both lexically precede the statement Foo b = a in function meow. Then the compiler can't determine which the type of b is. Is it correct or not?

编译器可以确定该代码块中 b 的类型。 B 的类型显然是 char* 而 a 的类型是 int

虽然在函数meow中两个具有不同含义的Foo在词法上都先于语句Foo b = a,但Foo定义为int先于Foo定义为char*。书上说name lookup过程不一样:

• First, the member declarations are compiled.
• Function bodies are compiled only after the entire class has been seen.

所以在第一步中,在编译成员声明时, Foo ausing Foo = char* 按顺序 编译 。而第一个Foo使用了Foo的外部定义,即int。然后,创建了一个内部作用域 Foo,类型为 char*。之后,编译器开始编译函数体。对于函数 meowFoo b 使用内部范围 Foo,即 char*,而对于 a,已在第一步中编译,是一个整数 Foo。这就是 转换错误 发生的原因。

评论3

I want to know is that why "the class may not subsequently redefine that name." But "an inner scope can redefine a name from an outer scope even if that name has already been used in the inner scope." –

R Sahu的观点(我认为这就是这本书想说的)是如果你真的想重新定义一个typedef 标识符,您只能在 class 的最开头执行此操作。因此上下文中不会有任何关于该标识符的"ambiguity"。

总结:

允许:

(这不能在 g++ 中编译(因为标准禁止这个)但可以在 Visual Studio 中编译,因为逻辑上这里没有冲突。)

typedef double Money;
class Account {
    public:
        Money balance() { return bal; }
    private:
        typedef double Money;
        Money bal;
};

很容易造成这样的事情:

(不能同时在g++和Visual Studio中编译,因为逻辑上这里有冲突。)

using Foo = int;

struct X {
    Foo a;    // ::Foo, i.e., int
    void meow() { 
        Foo b = a; // X::Foo; error: no conversion from int to char*
    }
    using Foo = char*;
};

所以如果你真的想在 class 中重新定义一个 typedef 标识符。只做这个:

(可以在 g++ 和 Visual Studio 中编译,因为逻辑上这里没有冲突,标准只允许这样。)

typedef double Money;

class Account {
    public:
        typedef double Money;  
        //put the redefine statement in the very beginning
        Money balance() { return bal; }
    private:
        Money bal;
};

PS:

解释代码:

typedef double Money;
class Account {
    public:
        Money balance() { return bal; }
    private:
        typedef double Money;
        Money bal;
};

这些代码逻辑上是正确的,但标准它被禁止了。同上编译步骤,先编译函数balance的声明,所以这里的Money就是外面的Money。然后编译 typedef double Money 我们得到一个内部作用域 Money 并且 bal 的类型是 Account::Money 而不是外部作用域。

所以实际上你可以用 Visual Studio 编译器来做到这一点,但不能用 g++:

typedef double Money;
class Account {
    public:
        Money shit = 12.34; //outside money, type is double
    private:
        typedef string Money;  
        Money bal;   //bal is string not double
};

感谢其他两位回答的启发。我的 post 中有一些预测,这是我个人的推论。如有不妥欢迎指正。