Pourquoi Clang et MSVC n'aiment-ils pas une déclaration typedef de membre avec un jeu redondant de parenthèses?
Considérer
using foo = int;
struct A {
typedef A (foo)();
};
GCC et ICC acceptent l'extrait, tandis que Clang et MSVC le rejettent. Le message d'erreur de Clang est
<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.
Et MSVC dit
<source>(4,15): error C2091: function returns function typedef A (foo)(); ^
( démo en direct )
Pourquoi Clang et MSVC produisent-ils cette erreur? Quels compilateurs sont corrects?
(Je recherche spécifiquement une citation de la norme ou de tout rapport de défaut.)
Réponses
Clang est faux: foo
dans la déclaration typedef in A
ne fait pas référence à la portée de l'espace de noms typedef-name foo
Wrt les règles standard, la déclaration d'alias d'espace de noms / portée englobante
using foo = int;
est un hareng rouge; dans le cadre déclarative de la classe , A
il sera assombrie par les noms déclarés dansA
#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>,"");
La clé ici étant que typedef A (foo)();
déclare le nom foo
dans la région déclarative de A
, selon [dcl.spec] / 3 [c'est moi qui souligne ]:
Si un nom de type est rencontré lors de l'analyse d'un decl-specifier-seq , il est interprété comme faisant partie du decl-specifier-seq si et seulement s'il n'y a pas de spécificateur de type de définition précédent autre qu'un qualificatif cv dans le decl -spécifier-seq .
Plus précisément, cela signifie que dans la déclaration typedef
typedef A (foo)();
même s'il existe un typedef-name existant foo
, qui foo
n'est pas pris en compte dans la déclaration typedef, c'est-à-dire qu'il n'est pas considéré comme une partie de type-name du decl-specifier-seq de typedef A (foo)()
, comme cela A
a déjà été rencontré précédemment, et A
est un spécificateur de type de définition valide . Ainsi, l'exemple original:
using foo = int; struct A { typedef A (foo)(); };
peut être réduit à:
// (i)
struct A {
typedef A (foo)(); // #1
};
qui déclare le nom typedef foo
entre A
( A::foo
), où les paranthèses autour du nom sont redondants, et la déclaration typedef en # 1 peut également être écrite comme
// (ii)
struct A {
typedef A foo(); // #1
};
et peut également être introduit en utilisant une déclaration d'alias ( [dcl.typedef] / 2 ):
// (iii)
struct A {
using foo = A();
};
(i)
, (ii)
et (iii)
sont acceptés à la fois par GCC et Clang.
Enfin, nous pouvons noter que Clang accepte le programme suivant:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
et que le problème racine de l'exemple de l'OP est sans doute un bogue de Clang, où Clang ne parvient pas à adhérer à [dcl.spec] / 3 et interprète le typedef-name de la portée externe foo
comme faisant partie du decl-specifier-seq du déclaration typedef de portée interne, uniquement pour le cas où ce dernier a enveloppé le nom ombré foo
entre parantheses.
Clang et MSVC ignorent tous deux le typedef
spécificateur et lisent la déclaration comme celle d'un constructeur (c'est-à- A
dire le nom du constructeur) acceptant les types de paramètres (foo)
(c'est-à-dire (int)
) et «retournant» un type de fonction indiqué par les parenthèses de fin ()
.
Oui, les constructeurs n'ont pas de types de retour; mais si elles ont fait avoir des types de retour , ils auraient le type de retour A
, de sorte que le plus ()
à la fin fait ces compilateurs pense que vous avez maintenant un constructeur avec le type de retour le type de fonction A()
.
Ceci est pris en charge en notant que les déclarations «similaires» suivantes ont des messages d'erreur similaires:
A (foo)();
typedef ~A(foo)();
De plus, en ajoutant, static
nous pouvons obtenir un message d'erreur éclairant de MSVC:
A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static
Pour des solutions de contournement: sous Clang (mais pas MSVC), vous pouvez déplacer le typedef
spécificateur vers la droite, ou utiliser un spécificateur de type élaboré:
A typedef (foo)();
typedef struct A (foo)();
Sous tous les compilateurs, vous pouvez supprimer ou ajouter des parenthèses:
typedef A foo();
typedef A ((foo))();
Et vous pouvez toujours mettre à jour vers un alias de type:
using foo = A();
Vous changez la signification de foo
de int
à A()
lorsque vous le redéclarez à l'intérieur A
. Cela enfreint basic.scope.class # 2 :
Un nom N utilisé dans une classe S se référera à la même déclaration dans son contexte et lorsqu'il est réévalué dans le domaine complet de S. Aucun diagnostic n'est requis pour une violation de cette règle.
Puisqu'il s'agit d'IFNDR, tous les compilateurs sont conformes.