I have the following vector class
#include <cmath>
#include <cstdlib>
#include <string>
#include <array>
template<typename T>
class Vec2
{
public:
Vec2();
Vec2(T x1, T x2);
Vec2(const T * data);
using arr_t = std::array<T,2>;
Vec2<T>(const arr_t &o) : _x(o) {}
private:
arr_t _x;
};
It compiles fine in c++20 standard with clang10/linux, but then I have the following error on a Windows MinGW (gcc 11.2) port:
vec2.h:55:17: error: expected unqualified-id before 'const'
55 | Vec2<T>(const arr_t &o) : _x(o) {}
| ^~~~~
Someone in the comments noticed the extra in the last constructor declaration, which shouldn't be there
Is this a clang bug to accept this? (it compiles and run fine)
The shown snippet is valid for Pre-C++20 but not valid from C++20 & onwards as explained below.
Pre-C++20
From class.ctor#1.2:
1 -- Constructors do not have names. In a declaration of a constructor, the declarator is a function declarator of the form:
ptr-declarator ( parameter-declaration-clause ) noexcept-specifieropt attribute-specifier-seqopt
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:
1.2 -- in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is a class-name that names the current instantiation of the immediately-enclosing class template; or
(end quote)
This means that in C++17, we are allowed to use Vec2<T>(const arr_t &o); as the constructor declaration.
C++20
From class.ctor#1.1:
1 -- A constructor is introduced by a declaration whose declarator is a function declarator ([dcl.fct]) of the form:
ptr-declarator ( parameter-declaration-clause ) noexcept-specifieropt attribute-specifier-seqopt
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:
1.1 -- in a member-declaration that belongs to the member-specification of a class or class template but is not a friend declaration ([class.friend]), the id-expression is the injected-class-name ([class.pre]) of the immediately-enclosing entity or
So as we can see, the injected class name(which is Vec2 and not Vec2<T> in your example) is needed for declaring the ctor of a class template.
This means that your given code is not valid for C++20.
The same is also mentioned at diff.cpp17.class#2:
Affected subclauses: [class.ctor] and [class.dtor]
Change: A simple-template-id is no longer valid as the declarator-id of a constructor or destructor.
Rationale: Remove potentially error-prone option for redundancy.
Effect on original feature: Valid C++ 2017 code may fail to compile in this revision of C++.
For example:
template<class T>
struct A {
A<T>(); // error: simple-template-id not allowed for constructor
A(int); // OK, injected-class-name used
~A<T>(); // error: simple-template-id not allowed for destructor
};
Edit
Your code was well-formed per the original C++20 grammar, however there is an accepted defect report in C++20 that disallows a simple-template-id in a constructor declaration.
GCC is ahead of the game here in implementing the DR before the other compilers, it seems. (gcc ref).
The resolution is to change
Vec2<T>(const arr_t &o) : _x(o) {}
into
Vec2(const arr_t &o) : _x(o) {}
Original answer
Your code is well-formed per the C++ grammar.
Per [class.ctor.general], a class constructor that is defined within the class declaration should be an "id-expression" that consists of the "injected class name", which in most cases is meant to simply refer to the name of the class sans any template arguments (formally, an identifier instead of a simple-template-id).
However, the C++ grammar defines class name as being either a plain identifier (Vec2) or a simple-template-id (Vec2<T>) ([class.pre]).
I will note that clang (trunk) and MSVC (latest) both accept your code, while gcc (trunk) fails. I believe this is a gcc bug.
Related
I was writing an out-of-class destructor definition for a class template when I noticed that the program compiles with clang with c++17 and c++20 and also with gcc with c++17 but rejected with gcc c++20. Demo.
template<typename T>
struct C
{
~C();
};
template<typename T>
C<T>::~C<T>() //accepted by compilers
{
}
int main()
{
C<int> c;;
}
The result of the above program is summarized in the below table:
Compiler
C++ Version
Accepts-Code
GCC
C++17
Yes
GCC
C++20
No
GCC
C++2b
No
Clang
C++17
Yes
Clang
C++20
Yes
Clang
C++2b
Yes
MSVC
C++17
Yes
MSVC
C++20
Yes
As we can see in the above both of the compilers accept the code except that gcc with c++20 and onwards reject it with the error error: template-id not allowed for destructor.
So, my question is which compiler is right here(if any).
The program is ill-formed atleast starting from c++20 and clang and msvc are wrong in accepting the code with c++20 and onwards.
Note that the change in wording for class.dtor was introduced in C++23 via p1787r6-class.dtor and seems to be a DR for C++20.
So, the code is ill-formed from C++20 and onwards which can be seen from: class.dtor#1.2 which states that:
1 A declaration whose declarator-id has an unqualified-id that begins with a ~ declares a prospective destructor; its declarator shall be a function declarator ([dcl.fct]) of the form
ptr-declarator ( parameter-declaration-clause ) noexcept-specifieropt attribute-specifier-seqopt
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:
1.2 otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier.
(emphasis mine)
And since the class-name is the injected-class-name C and not C<T> in our example, the correct way to write an out of class implementation for the destructor would be as shown below:
template<typename T>
struct C
{
~C(); //this is an ordinary destructor(meaning it is not templated)
};
template<typename T>
//-----v----------------> C is the injected-class-name and not C<T>
C<T>::~C()
{
}
int main()
{
C<int> c;;
}
Demo
Here is the clang bug report:
Clang accepts invalid out of class definition for destructor
Here is msvc bug report:
MSVC accepts invalid out of class definition for a destructor of a class template with c++20
For further reading
One can also refer to:
Why destructor cannot be template?
Error: Out-of-line constructor cannot have template arguments C++.
template<typename T> someclass<T>::someclass<T>() is not allowed when providing an out of class definition for a constructor and a destructor with any c++ version.
I don't think it's true that this was invalid in C++17.
CWG 1435 introduced the wording that allowed the code (that wording can still be seen in the context for the [class.dtor] changes in CWG 2337):
in a declaration at namespace scope or in a friend declaration, the id-expression is nested-name-specifier ~class-name and the class-name names the same class as the nested-name-specifier."
This allows the code, because C<T>::~C<T> is an id-expression of that form. The class-name in the destructor ~C<T> names the same class as in C<T>::. That wording was present in C++17 and C++20.
P1787R6 Declarations and where to find them changed that wording to:
otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier."
This no longer allows ~C<T> because the injected-class-name is just C.
This seems like a breaking change that was not obvious from the revision history of the r5 paper, which includes:
Required destructor declarations to use the injected-class-name, avoiding name lookup
Simplified lookup for destructors
Specified that < begins a template argument list in a destructor name
All compilers (GCC, Clang, EDG and MSVC) accept the program in C++17 mode, and I don't think it's at all clear that the code was always invalid in older standards, as OP claims. It seems to be a possibly-unintentional change in C++23. Edit: I'm reliably informed by Jason Merrill that it was an intentional change for C++23, but not a DR for C++17. See his comment on the other answer.
This question already has an answer here:
Is it possible to call templated user-defined conversion operator with explicit template arguments?
(1 answer)
Closed 2 years ago.
Consider the following program:
struct A {
template <typename T>
operator T() { return T{}; }
};
int main() {
(void) A{}.operator int(); // (A)
(void) A{}.template operator int(); // (B)
}
(A) is accepted by both GCC and Clang, whereas (B) is accepted only by GCC but rejected by Clang with the following error message:
error: expected template name after 'template' keyword in nested name specifier
(void) A{}.template operator int(); // (B)
^~~~~~~~~~~~
Afaict, (B) should be legal, as per [temp.names]/5:
A name prefixed by the keyword template shall be a template-id or the name shall refer to a class template or an alias template. [ Note: The keyword template may not be applied to non-template members of class templates. — end note ] [ Note: As is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or . is not dependent on a template-parameter, or the use does not appear in the scope of a template. — end note ]
and as the prohibitions governed by [temp.names]/4 does not apply:
The keyword template is said to appear at the top level in a qualified-id if it appears outside of a template-argument-list or decltype-specifier. [...] an optional keyword template appearing at the top level is ignored. [...]
and, at most, only state that the keyword should be ignored (but not that the program is ill-formed).
I have not found any clause in [class.conv.fct] or [temp.deduct.conv] that conflicts with this argument.
Question
Is it legal to prefix explicit access to a conversion function template with the template keyword?
I have tested and repeated the compilers' behaviour above with various GCC and Clang versions for various language standard versions, but for the scope of this question, we may focus on GCC 10.1.0 and Clang 10.0.0 for -std=c++17.
GCC is wrong to accept the program: a conversion function template name (conversion-function-id) is not a template-id
This is CWG Defect Report 96, which has, as of GCC 10, has not yet been addressed. The related bug GCC ticket 55588 mentions that it is being implemented for GCC 11.
As is covered in the answer to Is it possible to call templated user-defined conversion operator with explicit template arguments?, a conversion function template name does not name a template-id; quoting the grammar from template-id from [temp.names]/1:
simple-template-id:
template-name < template-argument-list_opt>
template-id:
simple-template-id
operator-function-id < template-argument-list_opt>
literal-operator-id < template-argument-list_opt>
template-name:
identifier
Thus, as is quoted in the OP, as per [temp.names]/5, the template keyword may not be used to prefix the name of a conversion function template, as the latter is not a template-id.
It may be interesting to point out that Clang does not reject using the keyword when referring to an operator-function-id (which is a template-id):
struct A {
template <typename T>
T operator+() { return T{}; }
};
int main() {
(void) A{}.operator+<int>(); // (A)
(void) A{}.template operator+<int>(); // (B)
}
That GCC accepts the OP's program is, as covered above, a bug, as it violates [temp.names]/5.
The following code snippet:
struct a
{
[[nodiscard]] friend int b();
};
Produces this error when compiling on clang++ (trunk 342102) with -std=c++17:
<source>:3:5: error: an attribute list cannot appear here
[[nodiscard]] friend int b();
^~~~~~~~~~~~~
Removing friend or adding a body to b prevents the error.
g++ (trunk) compiles the code just fine.
Live example on godbolt: https://gcc.godbolt.org/z/ttTDuZ
Is this a clang++ bug? Or is there some rule in the Standard that makes this code ill-formed?
If clang++ is correct, what's the proper way of marking a friend member function as [[nodiscard]]?
Per [dcl.attr.grammar]/5
Each attribute-specifier-seq is said to appertain to some entity or statement, identified by the syntactic context where it appears ([stmt.stmt], [dcl.dcl], [dcl.decl]). If an attribute-specifier-seq that appertains to some entity or statement contains an attribute or alignment-specifier that is not allowed to apply to that entity or statement, the program is ill-formed. If an attribute-specifier-seq appertains to a friend declaration, that declaration shall be a definition. No attribute-specifier-seq shall appertain to an explicit instantiation.
emphasis mine
So, clang is right here. If you have an attribute, the function must have a definition if it is a friend function.
I am mainly referring to the C++03 standard but, after a quick glance, it should also be applicable to the C++11 standard.
The following code compiled and executed successfully in VC++2010:
template<typename T>
class CC {
public:
T f(T a) {
return a*a;
}
};
template<>
class ::CC<int> { //<--- ::CC<int> syntax allowed by VC++2010, but is it non-standard ?
public:
int f(int a) {
return a*a;
}
};
int main(int argc, _TCHAR* argv[])
{
::CC<int> c;
}
Notice the ::CC<int> syntax to refer to the template defined in the global namespace. This is not the same as the NamespaceA::CC<int> syntax where the :: operator is preceded by something. With some other tools, I tried to parse this using the grammar strictly from the C++03 but it gave me errors and it seems to me that the standard accepts only NamespaceA::CC<int> form in the class head declaration.
On a closer look, the issue is that the class-head is defined by this grammar in the standard:
class-head:
class-key identifier(optional) base-clause(optional)
class-key nested-name-specifier identifier base-clause(optional)
class-key nested-name-specifier(optional) template-id base-clause(optional)
And since nested-name-specifier is of the form AA::bb::..., it doesn't accept my ::CC.
My question is, why the C++ standard doesn't allow the ::CC form? Is it just my incorrect interpretation of the standard grammar? Should the proper grammar looks like this:
class-head:
...
class-key '::'(optional) nested-name-specifier(optional) template-id base-clause(optional)
Note, the above form is really used by the standard somewhere else, say, in specifying declarator-id:
declarator-id:
id-expression
::(optional) nested-name-specifier(optional) class-name
From a comment by Columbo,
Of course a nested-name-specifier can be ::, and CC is the identifier, …?
That is not the case, at least not in the context of this question. Up until the 2014 version of the C++ standard, a bare double semicolon did not qualify as a nested-name-specifier. The 2003 version of the standard said a nested-name-specifier took on one of the two forms, in BNF:
class-or-namespace-name :: nested-name-specifieropt
class-or-namespace-name :: template nested-name-specifier
There was no room for a bare class ::CC to fit into this specification. The 2011 version added quite a bit to the BNF for a nested-name-specifier:
::opt type-name ::
::opt namespace-name ::
decltype-specifier ::
nested-name-specifier identifier ::
nested-name-specifier templateopt simple-template-id ::
This still left no room for class ::CC. The 2014 version of the standard finally addressed this by saying a nested-name-specifier is one of
::
type-name ::
namespace-name ::
decltype-specifier ::
nested-name-specifier identifier ::
nested-name-specifier templateopt simple-template-id ::
There are a number of ways to look at this interesting "feature". One is that this is a longstanding bug in the language specification, first identified in 2002 as issue #355. One of the jobs of a compiler vendor is to identify and patch over bugs in the language specification, and then get those bugs fixed in an upcoming release of the standard. From this point of view, template<> class ::CC<int> {...} should compile.
An alternative point of view is that this was not a bug. The BNF for a nested-name-specifier in both the 2003 and 2011 versions of the standard were quite clear, and thus template<> class ::CC<int> {...} should not compile. Whether this was an unfortunate misfeature or a deliberate feature didn't matter. The code in the question should not compile from the perspective of this point of view.
Which point of view is correct is debatable. That the issue that first reported this discrepancy was not rejected was a sign that there was some meat to that report. On the other hand, that nothing was done about it through two revisions of the standard also says something.
That said, now that the standard has been clarified, there is a bug in newer releases of GCC because even if one specifies --std=c++14, they do not allow template<> class ::CC<int> {...} to compile.
In the C++ draft, the nested-name-specifier is mentioned in [class].11:
If a class-head-name contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers, or in an element of the inline namespace set ([namespace.def]) of that namespace (i.e., not merely inherited or introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration. In such cases, the nested-name-specifier of the class-head-name of the definition shall not begin with a decltype-specifier.
And it can of course also be :: according to [expr.prim.id.qual].
In your code, you're using class ::CC<int> in a template class specialization, for which [temp.expl.spec].2 also applies:
An explicit specialization shall be declared in a namespace enclosing the specialized template. An explicit specialization whose declarator-id or class-head-name is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline ([namespace.def]), any namespace from its enclosing namespace set. Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later ([namespace.memdef]).
Hence, I think, using a qualified name should be okay.
Inspired by the code in this answer. Consider:
template<class>
class A { };
int main()
{
A<float> a(A<float>::A<int>());
return 0;
}
Is this code
ill-formed, because A<float>::A names the constructor (per §3.4.3.1 [class.qual]/p2) and cannot be used in this context (plus the <int> would complete fail to parse anyway), or
well-formed, with A<float>::A being the injected-class-name, used as a template-name (§14.6.1 [temp.local]), such that A<float>::A<int> means exactly the same as A<int>, and a being declared as a function (due to the most vexing parse)?
g++ says 1. clang says 2, and so does ICC 13. Which compiler is correct?
gcc is correct; your snippet is ill-formed!
// reduced testcase
template<class T>
class A { };
int main () {
A<float>::A<int> x; // ill-formed, bug in `clang` and `icc`
}
In the above reduced testcase we have a nested-name-specifier, A<float>::, followed by an unqualified-id A, which is then followed by some gibberish (<int>).
This is because the context in which the nested-name-specifier appears mandates that, during a look-up, function names are included (meaning that the constructor is found first, and the expression is ill-formed).
Relevant Bug Reports:
llvm.org/bugs/ - #8263; Incorrect constructor name resolution
How to circumvent the "problem"?
There are contexts in which member names that are looked up through a nested-name-specifier (that nominates a class) shall not include functions (hence, contexts where the constructor is not found), below are a few examples:
template<class T>
struct A {
typedef T value_type;
};
struct A<float>::A<int> x; // ok, context: elaborate-type-specifier
typename A<float>::A<int> (); // ok, context: [expr.type.conv]p1
A<float>::A::value_type x; // ok, context: nested-name-specifier
struct X : A<float>::A<int> { }; // ok, context: base-specifier
What does the Standard say?
3.4.3.1p2 Class members [class.qual]
In a lookup in which function names are not ignored88 and the nested-name-specifier nominates a class C:
if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9), or
in a using-declaration (7.3.3) that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the *nested-name-specicifier,
the name is instead considered to name the constructor of class C.
[ Note: ... ]
Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor or in a using-declaration.
88. Lookups in which function names are ignored include names appearing in a nested-name-specifier, an elaborated-type-specifier, or a base-specifier.
14.6.1p2 Locally declared names [temp.local]
Like normal (non-template) classes, class templates have an injected-class-name
(Clause 9). The injected-class-name can be used as a template-name or a
type-name.
When it is used with a template-argument-list, as a
template-argument for a template template-parameter, or as the final
identifier in the elaborated-type-specifier of a friend class template
declaration, it refers to the class template itself.
Otherwise, it is
equivalent to the template-name followed by the template-parameters of the
class template enclosed in <>.