Dlaczego Clang i MSVC nie lubią deklaracji typu elementu członkowskiego z nadmiarowym zestawem nawiasów?
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
Clang jest błędny: foo
w deklaracji typedef in A
nie 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 A
bę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ę foo
w 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 foo
nie 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 A
już napotkał poprzedni do niego i A
jest 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 foo
w 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ę foo
w cieniu w parantezach.
Zarówno Clang, jak i MSVC ignorują typedef
specyfikator i odczytują deklarację jako konstruktora (czyli A
nazwę 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ąć typedef
specyfikator 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();
Zmieniasz znaczenie foo
z int
na, 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.