为什么 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)();
              ^

(live demo)

为什么 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]/3A 的声明区域内声明 名称 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 在括号中。