ClangとMSVCが、冗長な括弧のセットを持つメンバーtypedef宣言を好まないのはなぜですか?

Dec 05 2020

検討する

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でこのエラーが発生するのはなぜですか?どのコンパイラが正しいですか?

(私は特に標準または欠陥レポートからの見積もりを探しています。)

回答

2 dfrib Dec 06 2020 at 20:13

Clangが間違っfooています:のtypedef宣言でA名前空間スコープtypedef-nameを参照していません foo

標準ルール、それを囲む名前空間/スコープエイリアス宣言

using foo = int;

赤いニシンです。クラスの宣言型の範囲内Aには、名前の陰になります宣言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>,"");

ここで重要なのは、[dcl.spec] / 3 [強調鉱山]のように、の宣言型領域内で名前をtypedef A (foo)(); 宣言することです。fooA

場合型名を解析中に遭遇する赤緯指定子-配列を、それの一部として解釈され赤緯指定子-SEQ 場合に以前のが存在しない場合にのみ、 定義型指定子は、以外CV-修飾子赤緯-指定子-seq

具体的には、これはtypedef宣言で

typedef A (foo)();

既存がある場合でもtypedefは名 foofootypedefの宣言では考慮されていないが、つまりそれは次のようにみなされていないタイプ名の一部が前方の宣言指定子-seqのtypedef A (foo)()ように、Aすでにそれに以前遭遇してきた、とAあります有効なdefining-type-specifier。したがって、元の例:

using foo = int;

struct A {
    typedef A (foo)();
};

次のように減らすことができます:

// (i)
struct A {
    typedef A (foo)();  // #1
};

これfooAA::foo)でtypedef名を宣言します。ここで、名前の周りの括弧は冗長であり、#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 をのdecl-specifier-seqfoo一部として解釈します。スコープ内のtypedef宣言。後者がシャドウ名を括弧で囲んでいる場合のみ。foo

3 ecatmur Dec 05 2020 at 22:38

ClangとMSVCはどちらもtypedef指定子を無視し、宣言をコンストラクター(つまりA、コンストラクター名)の宣言として読み取り、パラメーター型(foo)(つまり)を受け入れ(int)、末尾の括弧で示される関数型を「返します」()

はい、コンストラクターには戻り値の型がありません。彼らは場合はやった戻り値の型を持っている彼らは、戻り値の型を持つことになりA、追加して、()最後には、これらのコンパイラは、あなたが今、戻り値の型関数の型を持つコンストラクタを持っていると思います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();
cigien Dec 05 2020 at 21:49

内で再宣言すると、の意味がfooからintに変わりA()ますA。これはbasic.scope.class#2に違反します:

クラスSで使用される名前Nは、そのコンテキストで、Sの完全なスコープで再評価されるときに、同じ宣言を参照するものとします。このルールの違反に対して診断は必要ありません。

これはIFNDRであるため、すべてのコンパイラが準拠しています。