为什么 Clang 和 MSVC 不喜欢带有一组冗余括号的成员 typedef 声明?
Why do Clang and MSVC not like a member typedef declaration with a redundant set of parentheses?
考虑
using foo = int;
struct A {
typedef A (foo)();
};
GCC 和 ICC 接受代码段,而 Clang 和 MSVC 拒绝它。 Clang 的错误信息是
<source>:4:15: error: function cannot return function type 'void ()'
typedef A (foo)();
^
<source>:4:13: error: typedef name must be an identifier
typedef A (foo)();
^
2 errors generated.
MSVC 说
<source>(4,15): error C2091: function returns function
typedef A (foo)();
^
为什么 Clang 和 MSVC 会产生这个错误?哪些编译器是正确的?
(我专门从标准或任何缺陷报告中寻找报价。)
当您在 A
中重新声明时,您正在将 foo
的含义从 int
更改为 A()
。这违反了 basic.scope.class#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.
由于这是 IFNDR,所有编译器都符合要求。
Clang 和 MSVC 都忽略 typedef
说明符并将声明读取为构造函数的声明(即,A
是构造函数名称)接受参数类型 (foo)
(也就是说,(int)
) 和“returning”由尾部括号 ()
.
表示的函数类型
是的,构造函数没有 return 类型;但是如果他们 did 有 return 类型,他们会有 return 类型 A
,所以最后的附加 ()
使这些编译器认为你现在有一个 return 类型的构造函数类型 A()
.
注意到以下“类似”声明具有类似的错误消息,这得到了支持:
A (foo)();
typedef ~A(foo)();
此外,通过添加 static
,我们可以从 MSVC 获得一条说明性的错误消息:
A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static
解决方法:在 Clang(但不是 MSVC)下,您可以将 typedef
说明符移到右侧,或使用详细的类型说明符:
A typedef (foo)();
typedef struct A (foo)();
在所有编译器下您都可以删除或添加括号:
typedef A foo();
typedef A ((foo))();
并且您始终可以更新为类型别名:
using foo = A();
Clang 是错误的:A
中的 typedef 声明中的 foo
没有引用命名空间范围 typedef-name foo
W.r.t。标准规则,封闭的 namespace/scope 别名声明
using foo = int;
是一条红鲱鱼;在 class A
的声明范围内,它将被 declared in A
的名称遮蔽
#include <type_traits>
using foo = int;
struct A {
using foo = char;
foo x;
};
static_assert(std::is_same_v<foo, int>,"");
static_assert(std::is_same_v<A::foo, char>,"");
static_assert(std::is_same_v<decltype(A::x), char>,"");
这里的关键是 typedef A (foo)();
根据 [dcl.spec]/3 在 A
的声明区域内声明 名称 foo
] [强调我的]:
If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-seq if and only if there is no previous defining-type-specifier other than a cv-qualifier in the decl-specifier-seq.
具体来说,这意味着在 typedef 声明中
typedef A (foo)();
即使存在 typedef-name foo
,在 typedef 声明中也不考虑 foo
,即它不被视为 type- typedef A (foo)()
的 decl-specifier-seq 的 name 部分,因为 A
之前已经遇到过,并且 A
是有效的 定义类型说明符 。因此,原始示例:
using foo = int;
struct A {
typedef A (foo)();
};
可以减少为:
// (i)
struct A {
typedef A (foo)(); // #1
};
在 A
(A::foo
) 中声明了 typedef 名称 foo
,其中名称周围的括号是多余的,#1 处的 typedef 声明同样可以写成
// (ii)
struct A {
typedef A foo(); // #1
};
并且同样可以使用 别名声明 ([dcl.typedef]/2):
// (iii)
struct A {
using foo = A();
};
(i)
、(ii)
和 (iii)
被 GCC 和 Clang 接受。
最后,我们可能会注意到 Clang 接受以下程序:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
并且 OP 示例的根本问题可以说是 Clang 错误,其中 Clang 未能遵守 [dcl.spec]/3 并解释外部范围 typedef- name foo
作为内部范围 typedef 声明的 decl-specifier-seq 的一部分,仅适用于后者包裹了影子名称的情况foo
在括号中。
考虑
using foo = int;
struct A {
typedef A (foo)();
};
GCC 和 ICC 接受代码段,而 Clang 和 MSVC 拒绝它。 Clang 的错误信息是
<source>:4:15: error: function cannot return function type 'void ()' typedef A (foo)(); ^ <source>:4:13: error: typedef name must be an identifier typedef A (foo)(); ^ 2 errors generated.
MSVC 说
<source>(4,15): error C2091: function returns function typedef A (foo)(); ^
为什么 Clang 和 MSVC 会产生这个错误?哪些编译器是正确的?
(我专门从标准或任何缺陷报告中寻找报价。)
当您在 A
中重新声明时,您正在将 foo
的含义从 int
更改为 A()
。这违反了 basic.scope.class#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.
由于这是 IFNDR,所有编译器都符合要求。
Clang 和 MSVC 都忽略 typedef
说明符并将声明读取为构造函数的声明(即,A
是构造函数名称)接受参数类型 (foo)
(也就是说,(int)
) 和“returning”由尾部括号 ()
.
是的,构造函数没有 return 类型;但是如果他们 did 有 return 类型,他们会有 return 类型 A
,所以最后的附加 ()
使这些编译器认为你现在有一个 return 类型的构造函数类型 A()
.
注意到以下“类似”声明具有类似的错误消息,这得到了支持:
A (foo)();
typedef ~A(foo)();
此外,通过添加 static
,我们可以从 MSVC 获得一条说明性的错误消息:
A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static
解决方法:在 Clang(但不是 MSVC)下,您可以将 typedef
说明符移到右侧,或使用详细的类型说明符:
A typedef (foo)();
typedef struct A (foo)();
在所有编译器下您都可以删除或添加括号:
typedef A foo();
typedef A ((foo))();
并且您始终可以更新为类型别名:
using foo = A();
Clang 是错误的:A
中的 typedef 声明中的 foo
没有引用命名空间范围 typedef-name foo
W.r.t。标准规则,封闭的 namespace/scope 别名声明
using foo = int;
是一条红鲱鱼;在 class A
的声明范围内,它将被 declared in A
#include <type_traits>
using foo = int;
struct A {
using foo = char;
foo x;
};
static_assert(std::is_same_v<foo, int>,"");
static_assert(std::is_same_v<A::foo, char>,"");
static_assert(std::is_same_v<decltype(A::x), char>,"");
这里的关键是 typedef A (foo)();
根据 [dcl.spec]/3 在 A
的声明区域内声明 名称 foo
] [强调我的]:
If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-seq if and only if there is no previous defining-type-specifier other than a cv-qualifier in the decl-specifier-seq.
具体来说,这意味着在 typedef 声明中
typedef A (foo)();
即使存在 typedef-name foo
,在 typedef 声明中也不考虑 foo
,即它不被视为 type- typedef A (foo)()
的 decl-specifier-seq 的 name 部分,因为 A
之前已经遇到过,并且 A
是有效的 定义类型说明符 。因此,原始示例:
using foo = int; struct A { typedef A (foo)(); };
可以减少为:
// (i)
struct A {
typedef A (foo)(); // #1
};
在 A
(A::foo
) 中声明了 typedef 名称 foo
,其中名称周围的括号是多余的,#1 处的 typedef 声明同样可以写成
// (ii)
struct A {
typedef A foo(); // #1
};
并且同样可以使用 别名声明 ([dcl.typedef]/2):
// (iii)
struct A {
using foo = A();
};
(i)
、(ii)
和 (iii)
被 GCC 和 Clang 接受。
最后,我们可能会注意到 Clang 接受以下程序:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
并且 OP 示例的根本问题可以说是 Clang 错误,其中 Clang 未能遵守 [dcl.spec]/3 并解释外部范围 typedef- name foo
作为内部范围 typedef 声明的 decl-specifier-seq 的一部分,仅适用于后者包裹了影子名称的情况foo
在括号中。