You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CWG3160 [dcl.spec][class.ctor] Grammar ambiguity related to constructor declarations #741
It's common sense that #1 is a function pointer and #4 is a constructor, but the interpretation of #2 and #3 seems ambiguous: they could be function/variable declarations with redundant parentheses, or constructors with invalid static specifiers.
All compilers seem to choose the second interpretation, parsing #2 and #3 as invalid constructor declarations. However, they disagree with each other if static gets replaced with typedef or friend:
GCC and EDG seem to parse #5 and #6 as valid typedef declarations, and EDG seems to parse #7 as a valid friend declaration. Clang and MSVC parse them as invalid constructor declarations.
MSVC also considers A static (t3); to be a constructor declaration, but no one else does.
(3) should be disambiguated the same way as (4) because disambiguation ignores the fact that the chosen interpretation can violate a semantic constraint such as static not being allowed for a constructor.
The principle of [dcl.ambig.res] seems relevant here: interpreting A(t4); as a constructor declaration also makes the t4 part itself a declaration, while interpreting A(t4); as a non-static member declaration creates only one declaration, with t4 itself being a mere declarator-id. However, the wording of [dcl.ambig.res] does not seem to cover this situation, so perhaps it should be amended. This would also disambiguate (2) as an ill-formed constructor declaration.
In the namespace and concept cases, the variable/function interpretation must be taken because n1, n2, and c1 aren't valid parameter declarations in the context-sensitive grammar.
The u1 case should be disambiguated the same way as the typedef case because u1 can be syntactically interpreted as a CTAD placeholder ([dcl.type.simple]/3.1, [dcl.type.class.deduct]/2). It would be covered by the rule I suggested in my previous comment.
Actually, wait a second, I'm not sure how static A(t2)(); can be interpreted syntactically as a declaration of A. You can't put two parameter lists after the declarator-id, can you?
Actually, wait a second, I'm not sure how static A(t2)(); can be interpreted syntactically as a declaration of A. You can't put two parameter lists after the declarator-id, can you?
Here's a possible resolution. Add the following subsection to the end of [class.mem]:
Ambiguity resolution [class.mem.ambig]
An ambiguity can arise in a member-declaration when a type-name is enclosed in parentheses. In this case, the choice is between the declaration of a function with an unnamed parameter and the declaration of a member with redundant parentheses around the declarator-id. The resolution is to consider the type-name as a simple-type-specifier rather than a declarator-id. As in [stmt.ambig], the disambiguation is purely syntactic and can result in an ill-formed interpretation.
[Example:
typedef int t1, t2, t3;
template<class> struct u1;
struct A {
A(t1); // OK, declares constructor having unnamed parameter of type `int`, not a data member named `t1`
typedef A(t2); // error: constructor declaration with `typedef`, not typedef declaration of `t2`
friend A(t3)(); // error: declares function named `A` that returns a function with no return type, not function named `t3` that returns an `A`
A(u1); // error: placeholder for deduced class type cannot appear in this context ([dcl.type.class.deduct])
};
This ambiguity is not specific to member declarations: in
class A;
A(A);
the second line can be interpreted as either a nodeclspec-function-declaration, or a variable declaration with extra parentheses.
On this point, I'm inclined to say that the problem is that the grammar for nodeclspec-function-declaration is too general. The rule should be that in a declaration that could be a nodeclspec-function-declaration, if the declarator-id matches one of the forms described in [class.ctor], [class.dtor], or [class.conv.fct], then the declaration is always interpreted as a nodeclspec-function-declaration, and otherwise it's never interpreted as a nodeclspec-function-declaration.
changed the title [-][dcl.spec][class.ctor] Grammar ambiguity related to constructor declarations[/-][+]CWG3160 [dcl.spec][class.ctor] Grammar ambiguity related to constructor declarations[/+]on Feb 26, 2026
I think all prospective nodeclspec-function-declarations must have a declarator-id that is a qualified-id. Maybe we can simplify the disambiguation rule to that, instead of incorporating all of [class.ctor] etc.
I think all prospective nodeclspec-function-declarations must have a declarator-id that is a qualified-id. Maybe we can simplify the disambiguation rule to that, instead of incorporating all of [class.ctor] etc.
A variable declaration can also have a qualified-id though. All compilers treat #1 below as a valid variable declaration:
structA {};
structB : A {};
B::A(A); // #1
Maybe, instead of "if its declarator-id is a qualified-id", say something like "if the id-expression before the parentheses names a constructor ([class.qual])"?
My suggestion (#741 (comment)) would handle this example by disambiguating it as not a nodeclspec-function-declaration, because in B::A(A); the unqualified-idA of B::A (if that were taken to be the declarator-id) is not the injected-class-name of its lookup context (i.e. B).
Here's an updated version of the previous resolution for CWG3160, incorporating the feedback from the April 28 telecon to take into account some clues as to whether the declaration can declare a constructor. I would like to split off the grammar refactoring (removing the ability for a function with no return type to occur outside the top level in a declaration) into a separate issue and to not have it assigned to me (at least until I've had more time to look into it).
Add the following subsection to the end of [class.mem]:
Ambiguity resolution [class.mem.ambig]
An ambiguity can arise in a member-declaration when a type-name is enclosed in parentheses. In this case, the choice is between the declaration of a function with an unnamed parameter and the declaration of a member with redundant parentheses around the declarator-id. If the former interpretation would declare a function whose name is the injected-class-name of the enclosing class or class template and its decl-specifier-seq, if present, would contain neither friend nor a decl-specifier that is not permitted for a constructor ([class.ctor.general]), the former interpretation is chosen (i.e. the type-name is considered a simple-type-specifier rather than a declarator-id); otherwise, the latter interpretation is chosen. As in [stmt.ambig], the disambiguation is purely syntactic and can result in an ill-formed interpretation.
[Example:
typedef int t1, t2;
template<class> struct u1;
struct A {
A(t1); // OK, declares constructor having unnamed parameter of type `int`, not a data member named `t1`
typedef A(t2); // OK, declares `t2` as an alias for `A`
A(u1); // error: placeholder for deduced class type cannot appear in this context ([dcl.type.class.deduct])
};
— end example]
I also realized that we need an Annex C entry for the difference in parsing. In [diff.class], add after paragraph 1:
Affected subclause: [class.mem.ambig] Change: In C++, certain member declarations are interpreted as constructor declarations that would be data member declarations with redundant parentheses in C.
[Example:
typedef int t1, A;
struct A {
A(t1);
};
int f(struct A a) {
return a.t1; // valid in C, invalid in C++
}
—end example]
Rationale: C++ allows constructor declarations to use typedef-names when declaring parameter types. Effect on original feature: Change to semantics of well-defined feature. Difficulty of converting: Syntactic transformation. A data member declaration can be obtained by removing the pair of parentheses delimiting the constructor's parameter-declaration-clause. How widely used: Seldom.
For CWG3163, where we had a similar direction, here's updated drafting for [dcl.pre]/11:
A nodeclspec-function-declaration shall declare a constructor, destructor, or conversion functionA name-declaration that has the form of a nodeclspec-function-declaration is interpreted as a nodeclspec-function-declaration if its declarator-id has one of the forms specified for the declaration of a constructor, destructor, or conversion function at namespace scope ([class.ctor.general], [class.dtor], [class.conv.fct]). Otherwise, it is never interpreted as a nodeclspec-function-declaration.
[Note 6: Because a member function cannot be subject to a non-defining declaration outside of a class definition (11.4.2 [class.mfct]), a nodeclspec-function-declaration can only be used in a template-declaration (13.1 [temp.pre]), explicit-instantiation (13.9.3 [temp.explicit]), or explicit-specialization (13.9.4 [temp.expl.spec]).
[Example:
+ struct A {};+ struct B : A {};+ B::A(A); // OK, declares a variable named `A` having type `A`+ B::B(A); // error: out-of-line non-defining constructor declaration
—end example]
As a drive-by fix, [class.ctor.general]/1 should be edited so that any declaration where the declarator-id has one of the specified forms is always considered a (possibly ill-formed) constructor declaration. This brings it in line with [class.dtor] and [class.conv.fct] and justifies the striking of the sentence above:
A declaratordeclaration declares a constructor if it is a function declarator ([dcl.fct]) of
in a friend declaration ([class.friend]), the declarator-id is a qualified-id that names a constructor ([class.qual]);
otherwise, in a member-declaration that belongs to the member-specification of a class or class template, the declarator-id is the injected-class-name ([class.pre]) of the immediately-enclosing entity;
otherwise, the declarator-id is a qualified-id whose unqualified-id is the injected-class-name of its lookup context.
itsThe declarator of a constructor declaration shall have the form
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses. and the id-expression has one of the following forms:
in a friend declaration ([class.friend]), the id-expression is a qualified-id that names a constructor ([class.qual]);
otherwise, in a member-declaration that belongs to the member-specification of a class or class template, the id-expression is the injected-class-name ([class.pre]) of the immediately-enclosing entity;
otherwise, the id-expression is a qualified-id whose unqualified-id is the injected-class-name of its lookup context.
Constructors do not have names. In a constructor declaration, each decl-specifier in the optional decl-specifier-seq shall be friend, inline, constexpr, consteval, or an explicit-specifier. [Example 1: ... ]
Activity
cpplearner commentedon Aug 9, 2025
FWIW, here are cases with other kinds of context-dependent names:
With namespace names:
With class template name:
With concept name:
demo of all above: https://godbolt.org/z/qjY9zW913
t3nsor commentedon Aug 9, 2025
(3) should be disambiguated the same way as (4) because disambiguation ignores the fact that the chosen interpretation can violate a semantic constraint such as
staticnot being allowed for a constructor.The principle of [dcl.ambig.res] seems relevant here: interpreting
A(t4);as a constructor declaration also makes thet4part itself a declaration, while interpretingA(t4);as a non-static member declaration creates only one declaration, witht4itself being a mere declarator-id. However, the wording of [dcl.ambig.res] does not seem to cover this situation, so perhaps it should be amended. This would also disambiguate (2) as an ill-formed constructor declaration.t3nsor commentedon Aug 9, 2025
In the namespace and concept cases, the variable/function interpretation must be taken because
n1,n2, andc1aren't valid parameter declarations in the context-sensitive grammar.The
u1case should be disambiguated the same way as the typedef case becauseu1can be syntactically interpreted as a CTAD placeholder ([dcl.type.simple]/3.1, [dcl.type.class.deduct]/2). It would be covered by the rule I suggested in my previous comment.t3nsor commentedon Aug 9, 2025
Actually, wait a second, I'm not sure how
static A(t2)();can be interpreted syntactically as a declaration ofA. You can't put two parameter lists after the declarator-id, can you?cpplearner commentedon Aug 10, 2025
The grammar of noptr-declarator contains:
This seems to permit multiple parameters-and-qualifiers after the declarator-id.
t3nsor commentedon Aug 10, 2025
OK, yeah, that's weird, but I guess it means an (ill-formed) declaration of a function that returns a function.
t3nsor commentedon Oct 14, 2025
Here's a possible resolution. Add the following subsection to the end of [class.mem]:
lprv commentedon Oct 15, 2025
This ambiguity is not specific to member declarations: in
the second line can be interpreted as either a nodeclspec-function-declaration, or a variable declaration with extra parentheses.
t3nsor commentedon Feb 26, 2026
On this point, I'm inclined to say that the problem is that the grammar for nodeclspec-function-declaration is too general. The rule should be that in a declaration that could be a nodeclspec-function-declaration, if the declarator-id matches one of the forms described in [class.ctor], [class.dtor], or [class.conv.fct], then the declaration is always interpreted as a nodeclspec-function-declaration, and otherwise it's never interpreted as a nodeclspec-function-declaration.
jensmaurer commentedon Feb 26, 2026
CWG3160
[-][dcl.spec][class.ctor] Grammar ambiguity related to constructor declarations[/-][+]CWG3160 [dcl.spec][class.ctor] Grammar ambiguity related to constructor declarations[/+]t3nsor commentedon Mar 8, 2026
Jens, will there be a separate issue number for what was discussed in the most recent comments by me and lprv?
jensmaurer commentedon Mar 10, 2026
I think all prospective nodeclspec-function-declarations must have a declarator-id that is a qualified-id. Maybe we can simplify the disambiguation rule to that, instead of incorporating all of [class.ctor] etc.
jensmaurer commentedon Mar 10, 2026
CWG3163 for the latter issue.
cpplearner commentedon Apr 7, 2026
A variable declaration can also have a qualified-id though. All compilers treat
#1below as a valid variable declaration:Maybe, instead of "if its declarator-id is a qualified-id", say something like "if the id-expression before the parentheses names a constructor ([class.qual])"?
t3nsor commentedon Apr 28, 2026
My suggestion (#741 (comment)) would handle this example by disambiguating it as not a nodeclspec-function-declaration, because in
B::A(A);the unqualified-idAofB::A(if that were taken to be the declarator-id) is not the injected-class-name of its lookup context (i.e.B).t3nsor commentedon Jul 17, 2026
Here's an updated version of the previous resolution for CWG3160, incorporating the feedback from the April 28 telecon to take into account some clues as to whether the declaration can declare a constructor. I would like to split off the grammar refactoring (removing the ability for a function with no return type to occur outside the top level in a declaration) into a separate issue and to not have it assigned to me (at least until I've had more time to look into it).
Add the following subsection to the end of [class.mem]:
I also realized that we need an Annex C entry for the difference in parsing. In [diff.class], add after paragraph 1:
t3nsor commentedon Jul 17, 2026
For CWG3163, where we had a similar direction, here's updated drafting for [dcl.pre]/11:
As a drive-by fix, [class.ctor.general]/1 should be edited so that any declaration where the declarator-id has one of the specified forms is always considered a (possibly ill-formed) constructor declaration. This brings it in line with [class.dtor] and [class.conv.fct] and justifies the striking of the sentence above:
t3nsor commentedon Jul 26, 2026
@jensmaurer bump
jensmaurer commentedon Oct 5, 2026
CWG3160 is updated.
t3nsor commentedon Oct 5, 2026
There's drafting for CWG3163 above too
jensmaurer commentedon Oct 6, 2026
CWG3163 is updated.