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.
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.
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.
As far as I can see in the standard, the following code is valid. It compiles in MSVC1025.
const struct omg;
struct omg volatile;
int main()
{
return 0;
}
The qualifiers const and volatile seem purposeless in those declarations. They do not help nor hurt neither the compiler nor the programmer.
The standard does not seem bent on weeding out these "empty ambiguities". In the case of the empty declaration ;, it is explicitly allowed.
Are there other cases of tokens that, after preprocessing, are irrelevant for the meaning of the expression?
Both clang and gcc reject this code using -pedantic-errors. clang provides the following error:
error: 'const' is not permitted on a declaration of a type [-Werror,-Wmissing-declarations]
const struct omg;
^
error: 'volatile' is not permitted on a declaration of a type [-Werror,-Wmissing-declarations]
the draft C++ standard section 7.1.6.1 The cv-qualifiers [dcl.type.cv] says:
[...]If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list of the declaration shall
not be empty.[...]
Consider this code:
struct foo{};
int main() {
foo::foo a;
}
I would expect this to be well-formed, declaring a variable of type foo by the rule in [class]/2 (N4140, emphasis mine):
A class-name is inserted into the scope in which it is declared immediately after the class-name is seen.
The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name.
For purposes of access checking, the injected-class-name is treated as if it were a public member name.
clang 3.6.0 agrees with me, compiling the above code with no applicable warnings with -Wall -pedantic.
gcc 5.2.0 disagrees, providing the following error message:
main.cpp: In function 'int main()':
main.cpp:5:5: error: 'foo::foo' names the constructor, not the type
foo::foo a;
The above holds no matter how deep the nesting of injected class names, e.g. foo::foo::foo::foo.
Is there a rule which forces that construct to be interpreted as a constructor in that context, or is this agcc bug? Or am I interpreting the standards quote incorrectly?
It appears that clang is wrong in this case. The relevant exception I was looking for is in [class.qual]/2:
2 In a lookup in which function names are not ignored and the nested-name-specifier nominates a class C:
(2.1)
if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C, or
[...]
the name is instead considered to name the constructor of class C.
The standard has a near-equivalent (non-normative, obviously) example:
struct A { A(); };
struct B: public A { B(); };
A::A() { }
B::B() { }
B::A ba;// object of type A
A::A a;// error, A::A is not a type name
struct A::A a2;// object of type A
However, clang actually issues a correct diagnostic in this case:
error: qualified reference to 'A' is a constructor name rather than a type wherever a constructor can be declared
Perhaps clang interprets the line In a lookup in which function names are not ignored as In a lookup in which a constructor declaration is valid, but that doesn't seem to be a correct interpretation.
There is an existing bug for this in the clang bugzilla.
Relevant, but not an answer: The GCC people discussed exactly this for years and figured that it should not be accepted. They explicitly made this an error in GCC 4.5 and newer - in 4.4.7 it was accepted.
BTW: You probably want to use Clang's -Weverything instead of -Wall -pedantic when investigating such stuff.
I think this is the subject of language defect #147
which contains this example
class B { };
class A: public B {
A::B ab; // B is the inherited injected B
A::A aa; // Error: A::A is the constructor
};
At least gcc seems to believe that. :-)
Consider the following program:
extern class A;
int main() {}
Is this well-formed according to the c++ standard? If it is ill-formed is diagnostics required? I'm getting different results for different compilers:
Clang: No compiler errors (only a warning): http://melpon.org/wandbox/permlink/lhb8XNU01IyVhMnc
GCC: Compiler error: http://melpon.org/wandbox/permlink/mIH9qmNY4noI1sEc
Visual c++: No compiler errors (only a warning): http://webcompiler.cloudapp.net/
The program is ill-formed according to §7.1.1/1:
If a storage-class-specifier appears in a decl-specifier-seq, […]
the init-declarator-list of the declaration shall not be empty
(except for an anonymous union declared in a named namespace or in the
global namespace, which shall be declared static (9.5)).