Why do Clang and MSVC not like a member typedef declaration with a redundant set of parentheses?
Both Clang and MSVC are ignoring the typedef
specifier and reading the declaration as that of a constructor (that is, A
is the constructor name) accepting parameter types (foo)
(that is, (int)
) and "returning" a function type signified by the trailing parentheses ()
.
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();
Clang is wrong: foo
in the typedef declaration in A
does not refer to the namespace-scope typedef-name foo
W.r.t. the standard rules, the enclosing namespace/scope alias declaration
using foo = int;
is a red herring; within the declarative scope of class A
it will be shadowed by names declared in 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>,"");
The key here being that typedef A (foo)();
declares the name foo
within the declarative region of A
, as per [dcl.spec]/3 [emphasis mine]:
If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-seq if and only if there is no previous defining-type-specifier other than a cv-qualifier in the decl-specifier-seq.
Specifically, this means that in the typedef declaration
typedef A (foo)();
even if there is an existing typedef-name foo
, that foo
is not considered in the typedef declaration, namely it is not considered as a type-name part of the decl-specifier-seq of typedef A (foo)()
, as A
has already been encountered previous to it, and A
is a valid defining-type-specifier. Thus, the original example:
using foo = int; struct A { typedef A (foo)(); };
can be reduced to:
// (i)
struct A {
typedef A (foo)(); // #1
};
which declares the typedef name foo
in A
(A::foo
), where the paranthese around the name are redundant, and the typedef declaration at #1 can likewise be written as
// (ii)
struct A {
typedef A foo(); // #1
};
and can likewise be introduced using an alias declaration ([dcl.typedef]/2):
// (iii)
struct A {
using foo = A();
};
(i)
, (ii)
and (iii)
are accepted by both GCC and Clang.
Finally, we may note that Clang accepts the following program:
using foo = int;
struct A {
typedef A foo();
using bar = A();
};
static_assert(std::is_same_v<A::foo, A::bar>,"");
and that the root issue of the example of the OP is arguably a Clang bug, where Clang fails to adhere to [dcl.spec]/3 and interprets the outer-scope typedef-name foo
as part of the decl-specifier-seq of the inner-scope typedef declaration, only for the case where the latter has wrapped the shadowed name foo
in parantheses.