为什么不能在 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 a
和 using Foo = char*
按顺序 编译 。而第一个Foo
使用了Foo
的外部定义,即int
。然后,创建了一个内部作用域 Foo
,类型为 char*
。之后,编译器开始编译函数体。对于函数 meow
,Foo 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 中有一些预测,这是我个人的推论。如有不妥欢迎指正。
根据 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 classS
shall refer to the same declaration in its context and when re-evaluated in the completed scope ofS
. 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 a
和 using Foo = char*
按顺序 编译 。而第一个Foo
使用了Foo
的外部定义,即int
。然后,创建了一个内部作用域 Foo
,类型为 char*
。之后,编译器开始编译函数体。对于函数 meow
,Foo 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 中有一些预测,这是我个人的推论。如有不妥欢迎指正。