Reputation: 15918
Consider
using foo = int;
struct A {
typedef A (foo)();
};
GCC and ICC accept the snippet, while Clang and MSVC reject it. Clang's error message is
<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.
And MSVC says
<source>(4,15): error C2091: function returns function typedef A (foo)(); ^
Why do Clang and MSVC produce this error? Which compilers are correct?
(I'm specifically looking for quotation from the standard or any defect report.)
Upvotes: 19
Views: 746
Reputation: 73206
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.
Upvotes: 3
Reputation: 157444
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();
Upvotes: 4
Reputation: 60268
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.
Upvotes: 0