ClangとMSVCが、冗長な括弧のセットを持つメンバーtypedef宣言を好まないのはなぜですか?
検討する
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でこのエラーが発生するのはなぜですか?どのコンパイラが正しいですか?
(私は特に標準または欠陥レポートからの見積もりを探しています。)
回答
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)();
宣言することです。foo
A
場合型名を解析中に遭遇する赤緯指定子-配列を、それの一部として解釈され赤緯指定子-SEQ 場合に以前のが存在しない場合にのみ、 定義型指定子は、以外CV-修飾子に赤緯-指定子-seq。
具体的には、これはtypedef宣言で
typedef A (foo)();
既存がある場合でもtypedefは名 foo
、foo
typedefの宣言では考慮されていないが、つまりそれは次のようにみなされていないタイプ名の一部が前方の宣言指定子-seqののtypedef A (foo)()
ように、A
すでにそれに以前遭遇してきた、とA
あります有効なdefining-type-specifier。したがって、元の例:
using foo = int; struct A { typedef A (foo)(); };
次のように減らすことができます:
// (i)
struct A {
typedef A (foo)(); // #1
};
これfoo
はA
(A::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-seqのfoo
一部として解釈します。スコープ内のtypedef宣言。後者がシャドウ名を括弧で囲んでいる場合のみ。foo
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();
内で再宣言すると、の意味がfoo
からint
に変わりA()
ますA
。これはbasic.scope.class#2に違反します:
クラスSで使用される名前Nは、そのコンテキストで、Sの完全なスコープで再評価されるときに、同じ宣言を参照するものとします。このルールの違反に対して診断は必要ありません。
これはIFNDRであるため、すべてのコンパイラが準拠しています。