Warum mögen Clang und MSVC keine Typedef-Deklaration eines Mitglieds mit redundanten Klammern?
Erwägen
using foo = int;
struct A {
typedef A (foo)();
};
GCC und ICC akzeptieren das Snippet, während Clang und MSVC es ablehnen. Clangs Fehlermeldung lautet
<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.
Und MSVC sagt
<source>(4,15): error C2091: function returns function typedef A (foo)(); ^
( Live-Demo )
Warum erzeugen Clang und MSVC diesen Fehler? Welche Compiler sind richtig?
(Ich suche speziell nach einem Angebot aus dem Standard oder einem Fehlerbericht.)
Antworten
Clang ist falsch: foo
In der typedef-Deklaration A
bezieht sich in nicht auf den Namespace-Bereich typedef-name foo
Nach den Standardregeln die beiliegende Namespace / Scope-Alias-Deklaration
using foo = int;
ist ein roter Hering; Innerhalb des deklarativen Bereichs der Klasse A
wird es durch die in deklarierten Namen beschattetA
#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>,"");
Der Schlüssel hier ist, dass der Name innerhalb der deklarativen Region von typedef A (foo)();
deklariert wird , gemäß [dcl.spec] / 3 [ Schwerpunkt Mine]:foo
A
Wenn ein Typname auftritt , während ein Parsen Decl-specifier-Seq , wird es als Teil des interpretierten Decl-specifier-Seq , wenn und nur wenn es keine vorherigen ist die Definition-Typ-Spezifizierer andere als ein cv-Qualifikationsspiel in der Decl -Spezifizierer-seq .
Dies bedeutet insbesondere, dass in der typedef-Deklaration
typedef A (foo)();
selbst wenn es eine vorhandene ist typedef-Namen foo
, die foo
nicht in der typedef Erklärung betrachtet wird, nämlich wird es nicht als betrachtet Typname Teil des Decl-specifier-Seq von typedef A (foo)()
, wie A
vorherige es bereits aufgetreten ist, und A
ist ein gültiger Definitions-Typ-Spezifizierer . So ist das ursprüngliche Beispiel:
using foo = int; struct A { typedef A (foo)(); };
kann reduziert werden auf:
// (i)
struct A {
typedef A (foo)(); // #1
};
Dies deklariert den typedef-Namen foo
in A
( A::foo
), wobei die Paranthese um den Namen redundant sind, und die typedef-Deklaration bei # 1 kann ebenfalls als geschrieben werden
// (ii)
struct A {
typedef A foo(); // #1
};
und kann ebenfalls mit einer Alias-Deklaration ( [dcl.typedef] / 2 ) eingeführt werden:
// (iii)
struct A {
using foo = A();
};
(i)
, (ii)
Und (iii)
werden sowohl von GCC und Clang akzeptiert.
Schließlich können wir feststellen, dass Clang das folgende Programm akzeptiert:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
und dass die Wurzel Ausgabe von dem Beispiel der OP ist wohl ein Clang Bug, wo Clang zu nicht einhält [dcl.spec] / 3 und interpretiert den äußeren Umfang typedef-Namen foo
als Teil des DECL-Spezifizierer-seq der Typedef-Deklaration im inneren Bereich, nur für den Fall, dass dieser den schattierten Namen foo
in Klammern eingeschlossen hat.
Sowohl Clang als auch MSVC ignorieren den Bezeichner typedef
und lesen die Deklaration als die eines Konstruktors ( dh A
des Konstruktornamens), der Parametertypen akzeptiert (foo)
( dh (int)
) und einen durch die nachfolgenden Klammern gekennzeichneten Funktionstyp "zurückgibt" ()
.
Ja, Konstruktoren haben keine Rückgabetypen. Wenn sie jedoch Rückgabetypen hätten, hätten sie einen Rückgabetyp. A
Das zusätzliche ()
am Ende lässt diese Compiler denken, dass Sie jetzt einen Konstruktor mit dem Rückgabetyp als Funktionstyp haben A()
.
Dies wird durch die Feststellung unterstützt, dass die folgenden "ähnlichen" Deklarationen ähnliche Fehlermeldungen aufweisen:
A (foo)();
typedef ~A(foo)();
Durch Hinzufügen können static
wir auch eine leuchtende Fehlermeldung von MSVC erhalten:
A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static
Problemumgehungen: Unter Clang (aber nicht MSVC) können Sie den Bezeichner typedef
nach rechts verschieben oder einen ausgearbeiteten Typbezeichner verwenden:
A typedef (foo)();
typedef struct A (foo)();
Unter allen Compilern können Sie Klammern entfernen oder hinzufügen:
typedef A foo();
typedef A ((foo))();
Und Sie können jederzeit auf einen Typalias aktualisieren:
using foo = A();
Sie ändern die Bedeutung von foo
von int
bis, A()
wenn Sie es im Inneren neu deklarieren A
. Dies verstößt gegen basic.scope.class # 2 :
Ein in einer Klasse S verwendeter Name N muss sich in seinem Kontext und bei einer Neubewertung im vollständigen Geltungsbereich von S auf dieselbe Erklärung beziehen. Für einen Verstoß gegen diese Regel ist keine Diagnose erforderlich.
Da dies IFNDR ist, sind alle Compiler konform.