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 선언에서 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>,"");
여기서 핵심 은 [dcl.spec] / 3 [ emphasis mine]에 따라의 선언 영역 내 에서 이름 을 typedef A (foo)();
선언 하는 것입니다.foo
A
경우 유형 이름을 파싱하는 동안 발생 자명 한 일 입니 지정자-서열을 , 그것의 한 부분으로 해석됩니다 자명 한 일 입니 지정자-SEQ 더 이전이없는 경우만 정의 형 지정자 상 이외 이력서 - 예선 에서 자명 한 일 입니 -지정자 -seq .
특히 이것은 typedef 선언에서
typedef A (foo)();
기존이 경우에도 형식 정의 이름 foo
, foo
typedef를 선언에서 고려되지 않은, 즉 그것은으로 간주되지 않는 형식 이름 의 일부 자명 한 일 입니-지정-서열 의 typedef A (foo)()
등, A
이미 이전 발생했습니다, 그리고 A
이다 유효한 정의 유형 지정자 . 따라서 원래 예 :
using foo = int; struct A { typedef A (foo)(); };
다음과 같이 줄일 수 있습니다.
// (i)
struct A {
typedef A (foo)(); // #1
};
( ) foo
에서 typedef 이름을 선언합니다 . 여기서 이름 주위의 괄호는 중복되며 # 1의 typedef 선언도 마찬가지로 다음과 같이 작성할 수 있습니다.A
A::foo
// (ii)
struct A {
typedef A foo(); // #1
};
마찬가지로 별칭 선언 ( [dcl.typedef] / 2 )을 사용하여 도입 할 수 있습니다 .
// (iii)
struct A {
using foo = A();
};
(i)
, (ii)
및 (iii)
GCC와 연타 모두 사용할 수 있습니다.
마지막으로 Clang은 다음 프로그램을 허용합니다.
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
그리고 OP 예제의 근본 문제는 Clang 버그 일 것입니다. Clang이 [dcl.spec] / 3을 준수하지 못하고 외부 범위 typedef-name foo
을 decl-specifier-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를 위반합니다 .
클래스 S에 사용 된 이름 N은 문맥 상 동일한 선언을 참조하고 S의 완성 된 범위에서 재평가 될 때 참조해야합니다.이 규칙을 위반하는 경우 진단이 필요하지 않습니다.
이것은 IFNDR이므로 모든 컴파일러가 준수합니다.