Почему Clang и MSVC не любят объявление typedef члена с избыточным набором круглых скобок?
Рассматривать
using foo = int;
struct A {
typedef A (foo)();
};
GCC и ICC принимают фрагмент, а Clang и MSVC отклоняют его. Сообщение об ошибке Clang:
<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.
И MSVC говорит
<source>(4,15): error C2091: function returns function typedef A (foo)(); ^
( живая демонстрация )
Почему Clang и MSVC выдают эту ошибку? Какие компиляторы правильные?
(Я специально ищу цитату из стандарта или любого отчета о дефектах.)
Ответы
Clang неверен: foo
в объявлении typedef in A
не относится к области имен typedef-name foo
По стандартным правилам, включающее объявление псевдонима пространства имен / области видимости
using foo = int;
отвлекающий маневр; в декларативной области класса A
он будет затенен именами, объявленными вA
#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>,"");
Ключевым моментом здесь является то, что typedef A (foo)();
объявляется имя foo
в декларативной области A
, согласно [dcl.spec] / 3 [ выделено мной ]:
Если имя типа встречается при синтаксическом анализе Децл-спецификатор-SEQ , он интерпретируется как часть Децла-спецификатор-SEQ тогда и только тогда , когда нет предыдущего определение типа-спецификатора кроме сорта-классификатором в ДЕЦЛЕ -specifier-seq .
В частности, это означает, что в объявлении typedef
typedef A (foo)();
даже если есть существующее ЬурейеЕ имя foo
, что foo
не рассматриваются в декларации ЬурейеЙ, а именно это не рассматривается как имя-типа части Децли-спецификатор-SEQ из typedef A (foo)()
, так как A
уже сталкивались предыдущим к нему, и A
это действительный спецификатор определяющего типа . Итак, оригинальный пример:
using foo = int; struct A { typedef A (foo)(); };
сводится к:
// (i)
struct A {
typedef A (foo)(); // #1
};
который объявляет имя typedef foo
в A
( A::foo
), где скобки вокруг имени являются избыточными, а объявление typedef в # 1 также может быть записано как
// (ii)
struct A {
typedef A foo(); // #1
};
и аналогичным образом может быть введен с помощью объявления псевдонима ( [dcl.typedef] / 2 ):
// (iii)
struct A {
using foo = A();
};
(i)
, (ii)
и (iii)
принимаются как GCC, так и Clang.
Наконец, мы можем отметить, что Clang принимает следующую программу:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
и что корень вопрос на пример OP, возможно , звон ошибку, где Clang не в состоянии придерживаться [dcl.spec] / 3 и интерпретирует внешнюю область действия ЬурейеГо-имя foo
как часть ДЕЦЛА-спецификатор-SEQ из объявление typedef внутренней области видимости, только для случая, когда последнее заключило затененное имя foo
в скобки.
И Clang, и MSVC игнорируют typedef
спецификатор и читают объявление как объявление конструктора (то есть A
имя конструктора), принимая типы параметров (foo)
(то есть (int)
) и «возвращая» тип функции, обозначенный конечными круглыми скобками ()
.
Да, конструкторы не имеют возвращаемых типов; но если они действительно имеют типов возвращения они будут иметь тип возвращаемого значения A
, поэтому дополнительные ()
в конце делает эти компиляторы считают , что теперь у вас есть конструктор с типом возвращаемого типа функции A()
.
Это подтверждается тем, что следующие «похожие» объявления содержат похожие сообщения об ошибках:
A (foo)();
typedef ~A(foo)();
Кроме того, добавив, static
мы можем получить сообщение об ошибке от MSVC:
A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static
Для обходных путей: в Clang (но не в MSVC) вы можете переместить typedef
спецификатор вправо или использовать разработанный спецификатор типа:
A typedef (foo)();
typedef struct A (foo)();
Под всеми компиляторами вы можете убрать или добавить круглые скобки:
typedef A foo();
typedef A ((foo))();
И вы всегда можете обновить псевдоним типа:
using foo = A();
Вы меняете значение foo
с int
на, A()
когда заново объявляете его внутри A
. Это нарушает basic.scope.class # 2 :
Имя N, используемое в классе S, должно ссылаться на одно и то же объявление в его контексте и при повторной оценке в завершенной области S. Диагностика не требуется для нарушения этого правила.
Поскольку это IFNDR, все компиляторы соответствуют требованиям.