Mengapa Clang dan MSVC tidak menyukai deklarasi typedef anggota dengan sekumpulan tanda kurung yang berlebihan?

Dec 05 2020

Mempertimbangkan

using foo = int;

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

GCC dan ICC menerima cuplikan tersebut, sedangkan Clang dan MSVC menolaknya. Pesan kesalahan Clang adalah

<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.

Dan kata MSVC

<source>(4,15): error C2091: function returns function
    typedef A (foo)();
              ^

( demo langsung )

Mengapa Clang dan MSVC menghasilkan kesalahan ini? Kompiler mana yang benar?

(Saya secara khusus mencari kutipan dari standar atau laporan cacat apa pun.)

Jawaban

2 dfrib Dec 06 2020 at 20:13

Clang salah: foodalam deklarasi typedef di Atidak mengacu pada nama typedef- lingkup-namespace foo

Tulis aturan standar, deklarasi alias ruang lingkup / namespace yang melampirkan

using foo = int;

adalah ikan haring merah; dalam ruang lingkup deklaratif kelas Aitu akan dibayangi oleh nama yang dideklarasikan dalamA

#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>,"");

Kuncinya di sini adalah yang typedef A (foo)(); mendeklarasikan nama foodalam wilayah deklaratif A, sesuai [dcl.spec] / 3 [ penekanan saya]:

Jika nama-tipe ditemukan saat mengurai dec-specifier-seq , itu akan diinterpretasikan sebagai bagian dari dec-specifier-seq jika dan hanya jika tidak ada penentu-jenis-penentu sebelumnya selain cv-qualifier dalam deklarasi -specifier-seq .

Secara khusus, ini berarti bahwa dalam deklarasi typedef

typedef A (foo)();

bahkan jika ada ada typedef-nama foo , yang footidak dianggap dalam deklarasi typedef, yaitu tidak dianggap sebagai jenis-nama bagian dari decl-specifier-seq dari typedef A (foo)(), seperti Atelah ditemui sebelumnya untuk itu, dan Aadalah defining-type-specifier yang valid . Jadi, contoh aslinya:

using foo = int;

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

dapat direduksi menjadi:

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

yang mendeklarasikan nama typedef foodi A( A::foo), di mana paranthese di sekitar nama itu berlebihan, dan deklarasi typedef di # 1 juga bisa ditulis sebagai

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

dan juga bisa diperkenalkan menggunakan deklarasi alias ( [dcl.typedef] / 2 ):

// (iii)
struct A {
    using foo = A();
};

(i), (ii)dan (iii)diterima oleh GCC dan Clang.

Terakhir, kami dapat mencatat bahwa Clang menerima program berikut:

using foo = int;
struct A {
    typedef A foo();
    using bar = A();
};

static_assert(std::is_same_v<A::foo, A::bar>,"");

dan bahwa masalah akar dari contoh OP bisa dibilang adalah bug Clang, di mana Clang gagal untuk mematuhi [dcl.spec] / 3 dan menafsirkan nama- typedef- lingkup-luar foosebagai bagian dari dec-specifier-seq dari deklarasi typedef dalam lingkup, hanya untuk kasus di mana yang terakhir telah membungkus nama berbayang foodalam tanda kurung.

3 ecatmur Dec 05 2020 at 22:38

Baik Clang dan MSVC mengabaikan typedefspecifier dan membaca deklarasi sebagai konstruktor (yaitu, Anama konstruktor) menerima tipe parameter (foo)(yaitu, (int)) dan "mengembalikan" tipe fungsi yang ditandai dengan tanda kurung ().

Yes, constructors don't have return types; but if they did have return types they would have return type A, so the additional () at the end makes these compilers think that you now have a constructor with return type the function type A().

This is supported by noting that the following "similar" declarations have similar error messages:

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

Also, by adding static we can get an illuminating error message from MSVC:

A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static

For workarounds: under Clang (but not MSVC) you can move the typedef specifier to the right, or use an elaborated type specifier:

A typedef (foo)();
typedef struct A (foo)();

Under all compilers you can remove or add parentheses:

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

And you can always update to a type alias:

using foo = A();
cigien Dec 05 2020 at 21:49

You are changing the meaning of foo from int to A() when you redeclare it inside A. This violates basic.scope.class#2:

A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.

Since this is IFNDR, all compilers are conforming.