Clang과 MSVC가 중복 된 괄호 집합이있는 멤버 typedef 선언을 좋아하지 않는 이유는 무엇입니까?

Dec 05 2020

중히 여기다

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에서이 오류가 발생하는 이유는 무엇입니까? 어떤 컴파일러가 맞습니까?

(특히 표준 또는 결함 보고서에서 인용을 찾고 있습니다.)

답변

2 dfrib Dec 06 2020 at 20:13

Clang이 잘못되었습니다 : footypedef 선언에서 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)(); 선언 하는 것입니다.fooA

경우 유형 이름을 파싱하는 동안 발생 자명 한 일 입니 지정자-서열을 , 그것의 한 부분으로 해석됩니다 자명 한 일 입니 지정자-SEQ 더 이전이없는 경우만 정의 형 지정자 상 이외 이력서 - 예선 에서 자명 한 일 입니 -지정자 -seq .

특히 이것은 typedef 선언에서

typedef A (foo)();

기존이 경우에도 형식 정의 이름 foo , footypedef를 선언에서 고려되지 않은, 즉 그것은으로 간주되지 않는 형식 이름 의 일부 자명 한 일 입니-지정-서열typedef A (foo)()등, A이미 이전 발생했습니다, 그리고 A이다 유효한 정의 유형 지정자 . 따라서 원래 예 :

using foo = int;

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

다음과 같이 줄일 수 있습니다.

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

( ) foo에서 typedef 이름을 선언합니다 . 여기서 이름 주위의 괄호는 중복되며 # 1의 typedef 선언도 마찬가지로 다음과 같이 작성할 수 있습니다.AA::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 foodecl-specifier-seq 의 일부로 해석합니다 . 내부 범위 typedef 선언은 후자가 foo괄호로 숨겨진 이름 을 래핑 한 경우에만 해당됩니다 .

3 ecatmur Dec 05 2020 at 22:38

Clang과 MSVC는 모두 typedef지정자를 무시하고 선언을 생성자 (즉, A생성자 이름) 의 선언을 읽고 매개 변수 유형 (foo)(즉, (int)) 을 허용 하고 후행 괄호로 표시되는 함수 유형을 "반환"합니다 ().

예, 생성자에는 반환 유형이 없습니다. 그들이하지만 만약 반환 형식을 가지고 그들은 반환 유형을했을 A추가, 그래서 ()끝이 컴파일러는 지금 반환 형식 함수 형식의 생성자가 있다고 생각합니다 A().

이는 다음 "유사한"선언에 유사한 오류 메시지가 있다는 점에 유의하여 지원됩니다.

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

또한 추가하면 staticMSVC에서 조명 오류 메시지를 얻을 수 있습니다.

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();
cigien Dec 05 2020 at 21:49

당신의 의미 변화 foo에서 intA()당신이 내부를 재 선언 할 때를 A. 이것은 basic.scope.class # 2를 위반합니다 .

클래스 S에 사용 된 이름 N은 문맥 상 동일한 선언을 참조하고 S의 완성 된 범위에서 재평가 될 때 참조해야합니다.이 규칙을 위반하는 경우 진단이 필요하지 않습니다.

이것은 IFNDR이므로 모든 컴파일러가 준수합니다.