Dlaczego Clang i MSVC nie lubią deklaracji typu elementu członkowskiego z nadmiarowym zestawem nawiasów?

Dec 05 2020

Rozważać

using foo = int;

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

GCC i ICC akceptują fragment, podczas gdy Clang i MSVC go odrzucają. Komunikat o błędzie Clanga to

<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.

A MSVC mówi

<source>(4,15): error C2091: function returns function
    typedef A (foo)();
              ^

( demo na żywo )

Dlaczego Clang i MSVC generują ten błąd? Które kompilatory są poprawne?

(W szczególności szukam wyceny ze standardowego lub dowolnego raportu o usterkach).

Odpowiedzi

2 dfrib Dec 06 2020 at 20:13

Clang jest błędny: foow deklaracji typedef in Anie odnosi się do nazwy typu typedef w zakresie przestrzeni nazw foo

Wpisz standardowe reguły, otaczającą deklarację aliasu przestrzeni nazw / zakresu

using foo = int;

to czerwony śledź; w ramach deklaratywnego zakresu klasy Abędzie przesłany przez nazwy zadeklarowane wA

#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>,"");

Kluczem tutaj jest, że typedef A (foo)(); deklaruje nazwę foow regionie od deklaratywnej A, zgodnie [dcl.spec] / 3 [ nacisk kopalni]:

Jeśli nazwa-typu zostanie napotkana podczas analizowania specyfikacji decl-specifier-seq , jest interpretowana jako część specyfikacji decl-specifier-seq wtedy i tylko wtedy, gdy nie ma wcześniejszego specyfikatora typu definiującego innego niż kwalifikator cv w decl -specifier-seq .

W szczególności oznacza to, że w deklaracji typu

typedef A (foo)();

nawet jeśli nie jest to istniejący typedef-name foo , który foonie jest uznawany w deklaracji typedef, a mianowicie, że nie jest traktowany jako nazwa-typu części dekl-specyfikującego-seq z typedef A (foo)(), jak Ajuż napotkał poprzedni do niego i Ajest prawidłowy specyfikator typu definiującego . Tak więc oryginalny przykład:

using foo = int;

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

można zredukować do:

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

który deklaruje nazwę typedef foow A( A::foo), gdzie paranty wokół nazwy są zbędne, a deklaracja typedef w # 1 może być również zapisana jako

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

i można je również wprowadzić za pomocą deklaracji aliasu ( [dcl.typedef] / 2 ):

// (iii)
struct A {
    using foo = A();
};

(i), (ii)i (iii)są akceptowane zarówno przez GCC, jak i Clang.

Na koniec możemy zauważyć, że Clang akceptuje następujący program:

using foo = int;
struct A {
    typedef A foo();
    using bar = A();
};

static_assert(std::is_same_v<A::foo, A::bar>,"");

i że emisja głównego przykładu PO jest prawdopodobnie błąd Clang gdzie Szczęk nie przylegają do [dcl.spec] / 3 i interpretuje zewnętrznej zakres typedef-nazwą foo jako część dekl-specyfikatora-seq z deklaracja typedef z zakresem wewnętrznym, tylko w przypadku, gdy ta ostatnia zawinęła nazwę foow cieniu w parantezach.

3 ecatmur Dec 05 2020 at 22:38

Zarówno Clang, jak i MSVC ignorują typedefspecyfikator i odczytują deklarację jako konstruktora (czyli Anazwę konstruktora), akceptując typy parametrów (foo)(to znaczy (int)) i „zwracając” typ funkcji oznaczony końcowymi nawiasami ().

Tak, konstruktory nie mają typów zwracanych; ale jeśli oni nie mają typów zwracanych musieliby typ zwracany A, więc dodatkowa ()w końcu sprawia, że te kompilatory pomyśleć, że teraz ma konstruktora z rodzaju zwraca typ funkcji A().

Można to zauważyć, zauważając, że następujące „podobne” deklaracje mają podobne komunikaty o błędach:

A (foo)();
typedef ~A(foo)();

Ponadto, dodając static, możemy uzyskać podświetlający komunikat o błędzie z MSVC:

A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static

Aby obejść problem: w Clang (ale nie MSVC) możesz przesunąć typedefspecyfikator w prawo lub użyć opracowanego specyfikatora typu:

A typedef (foo)();
typedef struct A (foo)();

Pod wszystkimi kompilatorami możesz usunąć lub dodać nawiasy:

typedef A foo();
typedef A ((foo))();

Zawsze możesz zaktualizować do aliasu typu:

using foo = A();
cigien Dec 05 2020 at 21:49

Zmieniasz znaczenie fooz intna, A()kiedy ponownie ogłaszasz je w środku A. To narusza basic.scope.class # 2 :

Nazwa N używana w klasie S powinna odnosić się do tej samej deklaracji w jej kontekście i po ponownej ocenie w pełnym zakresie S. Do naruszenia tej zasady nie jest wymagana żadna diagnostyka.

Ponieważ jest to IFNDR, wszystkie kompilatory są zgodne.