Warum mögen Clang und MSVC keine Typedef-Deklaration eines Mitglieds mit redundanten Klammern?

Dec 05 2020

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

2 dfrib Dec 06 2020 at 20:13

Clang ist falsch: fooIn der typedef-Deklaration Abezieht 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 Awird 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]:fooA

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 foonicht 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 Avorherige es bereits aufgetreten ist, und Aist 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 fooin 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 fooin Klammern eingeschlossen hat.

3 ecatmur Dec 05 2020 at 22:38

Sowohl Clang als auch MSVC ignorieren den Bezeichner typedefund lesen die Deklaration als die eines Konstruktors ( dh Ades 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. ADas 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 staticwir 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 typedefnach 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();
cigien Dec 05 2020 at 21:49

Sie ändern die Bedeutung von foovon intbis, 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.