I just stumbled upon the following differences between GCC and Clang concerning an explicitly defaulted constexpr ctor and some inheritance...
template <typename T>
struct A {
constexpr A() = default;
T v;
};
struct B : A<int> {
constexpr B() = default;
};
GCC immediately rejects the code while Clang allows to instantiate non-constexpr versions of both types. My guess is that Clang is probably correct, but I'm not 100% certain...
The problem boils down to:
is a constexpr constructor that default-initializes
some non-static data member of builtin type valid,
if it is not used?
tl;dr:
For a non-template constructor,
no, it is invalid to leave any non-static data members uninitialized.
For a template constructor, yes,
it is valid to have some (but not all, no diagnostic required)
instantiated template specializations
for which the instantiated constructor does not meet the requirements
of a constexpr constructor.
In this case, GCC is right, whereas Clang is wrong.
GCC gives the following error message which is very informative:
prog.cc:8:13: error: explicitly defaulted function 'constexpr B::B()' cannot be declared as 'constexpr' because the implicit declaration is not 'constexpr':
8 | constexpr B() = default;
| ^
prog.cc:3:13: note: defaulted constructor calls non-'constexpr' 'A<T>::A() [with T = int]'
3 | constexpr A() = default;
| ^
prog.cc:3:13: note: 'A<T>::A() [with T = int]' is not usable as a 'constexpr' function because:
prog.cc:4:5: note: defaulted default constructor does not initialize 'int A<int>::v'
4 | T v;
| ^
live demo
Note that the error is raised on the constructor of B,
instead of that of A,
whose constructor is merely "not usable as a constexpr function
because [the] defaulted default constructor
does not initialize int A<int>::v."
Per [dcl.constexpr]/4:
The definition of a constexpr constructor shall satisfy the following
requirements:
the class shall not have any virtual base classes;
each of the parameter types shall be a literal type.
In addition, either its function-body shall be = delete, or it
shall satisfy the following requirements:
[...]
every non-variant non-static data member and base class subobject
shall be initialized ([class.base.init]);
[...]
Here, v is of type int, and is not initialized.
Therefore, it seems that the constructor of A
cannot be declared constexpr.
However, [dcl.constructor]/6 says:
If the instantiated template specialization of a constexpr function
template or member function of a class template would fail to satisfy
the requirements for a constexpr function or constexpr constructor,
that specialization is still a constexpr function or constexpr
constructor, even though a call to such a function cannot appear in a
constant expression. If no specialization of the template would
satisfy the requirements for a constexpr function or constexpr
constructor when considered as a non-template function or constructor,
the template is ill-formed, no diagnostic required.
Therefore, the constructor of A that is declared constexpr
is actually valid,
even when instantiated for T = int!
The problem is the constructor of B.
B is an ordinary class (as opposed to a class template),
and for its constructor to be (merely) declared constexpr,
A<int> must have a constexpr constructor,
which is not the case.
Therefore, this code should be rejected, as GCC does.
(Note that both compilers reject initialization of such a type,
for example:
A a{};
B b{};
The above code is rejected by both compilers.)
As mentioned in a comment,
initialize A::v and GCC (and the standard) will be happy.
Related
In the next program struct B has immediate consteval default constructor, which does not initialize i field. Then this constructor is used to make a temporary and its i field remains untouched:
struct B {
bool b = true;
int i;
consteval B() {}
};
static_assert( B{}.b );
Clang and MSVC are fine with it. But GCC complains:
error: 'B{true}' is not a constant expression
7 | static_assert( B{}.b );
| ^
error: 'B()' is not a constant expression because it refers to an incompletely initialized variable
Demo: https://gcc.godbolt.org/z/x4n6ezrhT
Which compiler is right here?
Update:
I reported this issue to GCC: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104512
And it was closed with the explanation
This gives the hint that both MSVC and clang are incorrect really. EDG also implements correctly static_assert not being an immediate function context.
From cppreference's consteval specifier (since C++20):
The consteval specifier declares a function or function template to be
an immediate function,
...
An immediate function is a constexpr function, and must satisfy the
requirements applicable to constexpr functions or constexpr
constructors, as the case may be.
And if we go to cppreference's constexpr specifier (since C++11):
A constexpr function must satisfy the following requirements:
...
A constexpr constructor whose function body is not =delete; must satisfy the following additional requirements:
...
for the constructor of a class or struct, every base class sub-object
and every non-variant non-static data member must be initialized.
However, as #user17732522 accurately pointed out in a comment below, this last requirement applies only until C++20.
So I would say i doesn't need to be initialized in this case (Clang/MSVC are correct, gcc is wrong).
The following minimal example is rejected by both Clang and GCC for not initializing the array data-member:
class vector3
{
public:
constexpr vector3() = default;
private:
float m_data[3];
};
constexpr auto vec = vector3{};
Which yields the reasonably straight-forward error:
<source>:4:15: error: explicitly defaulted function 'constexpr vector3::vector3()' cannot be declared 'constexpr' because the implicit declaration is not 'constexpr':
4 | constexpr vector3() = default;
| ^~~~~~~
<source>:6:11: note: defaulted default constructor does not initialize 'float vector3::m_data [3]'
6 | float m_data[3];
| ^~~~~~
Live Example
The goal with the above code was to ensure that vector3 can be used in constant expressions via value-initialization (e.g. vector3{}), which will zero-initialize the sub-elements (m_data).
The error occurs due to the use of the constexpr keyword, and the fix is simply to remove the keyword and to allow default to correctly deduce whether this can be used in a constant expression:
class vector3
{
public:
vector3() = default;
private:
float m_data[3];
};
constexpr auto vec = vector3{}; // now works?
Curiously, this actually now works -- and is still able to produce a constant expression, with m_data being zero-initialized, as visible in the assembly for GCC (Similar exists in Clang, but with XOR instructions):
vec:
.zero 12
Live Example
My question is: How is it possible that = default produces a (valid) constexpr constructor, whereas constexpr ... = default fails due to it not being valid for constexpr?
This question appears to affect C++ versions prior to C++20 (C++11 through C++17). Was this changed in C++20?
Live Example
Yes, it's true that in C++20, the rules were changed so that a constexpr constructor is no longer required to initialize all non-static members and base class subobjects.
Prior to C++20, we have the interesting situation that your constructor cannot be declared constexpr, but objects of the vector3 type can still be used in constant expressions, because a default constructor that is explicitly defaulted on its first declaration is not actually called during value-initialization unless it is nontrivial (C++17 [dcl.init]/8.2) and thus the prohibition on calling non-constexpr functions within a constant expression is not triggered. This is not a compiler bug; it's just a quirk in the language.
I have the code:
class A {
public:
A() = default;
private:
int i = 1;
};
int main() {
const A a;
return 0;
}
It compiles fine on g++ (see ideone), but fails on clang++ with error:
default initialization of an object of const type 'const A' requires a user-provided default constructor
I reported this issue on LLVM bug-tracker and got it INVALID.
I see it absolutly pointless to try to convince the clang developers. On the other side, I don't see the reason for such restriction.
Can anyone advise, if the C++11 Standard somehow implies this code to be invalid? Or should I just report a bug to g++? Or maybe there is enough freedom in language rules to handle this code in many ways?
N3797 §8.5/7 says:
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.
There's no further example or explanation of this. I agree it seems pretty bizarre. Furthermore the rule was updated in C++11 to be more restrictive than it was in C++03, when class types needed user-declared constructors. (Your constructor is user-declared.)
The workaround is be to ask for value initialization using {}, or use Dietmar's clever out-of-class inline definition.
GCC does provide a diagnosis (and quite a nice one, referring to the newer C++11 requirements) if you add another member without an initializer.
private:
int i = 1;
int j;
unmem.cpp:11:11: error: uninitialized const ‘a’ [-fpermissive]
const A a;
^
unmem.cpp:1:7: note: ‘const class A’ has no user-provided default constructor
class A {
^
unmem.cpp:3:5: note: constructor is not user-provided because it is explicitly defaulted in the class body
A() = default;
^
unmem.cpp:7:9: note: and the implicitly-defined constructor does not initialize ‘int A::j’
int j;
The GCC source refers to DR 253, Why must empty or fully-initialized const objects be initialized? This is an open issue in the standard, last updated in August 2011 (post-C++11) with this note:
If the implicit default constructor initializes all subobjects, no initializer should be required.
Therefore whereas Clang complies with C++11 (and will comply as-is with C++14), GCC is implementing the latest thinking of the standardization committee.
Filed a GCC bug. I predict that you'll need -pedantic to get a diagnosis when (and if) the bug is fixed.
Note that you can turn your class easily into one which has a user-defined default constructor:
class A {
public:
A();
private:
int i = 1;
};
inline A::A() = default;
According to 8.4.2 [dcl.fct.def.default] paragraph 4:
... A special member function is user-provided if it is user-declared and not explicitly
defaulted or deleted on its first declaration. ...
This implicitly states that a function which is not explicitly defaulted on its first declaration is not user-provided. In combination with 8.5 [dcl.init] paragraph 6
... If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.
it seems clear that you cannot use a default constructor defaulted on its first declaration to initialize a const object. However, you can use a defaulted definition if it isn't the first declaration as is done in the code above.
Edit: The following is based on outdated information. I just went through N3797 and this is what I found:
§ 8.5/7 [dcl.init]
If a program calls for the default initialization
of an object of a const-qualified type T, T shall be a class type with
a user-provided default constructor.
Note the standard quote in the link below says user-declared.
The following program compiles in g++ but not clang++:
struct A {};
void f()
{
A const a;
}
And it might be related to this bug report where it was "fixed". g++ fails to compile it once it contains data members unless they're initialized. Note that int member = 1 will no longer make A a POD. Comparatively, clang++ rejects all permutations (empty classes and data members initialized or not.) For an interpretation of what the standard means by the following paragraph:
§ 8.5/9 [dcl.init] says:
If no initializer is specified for an object, and the object is of
(possibly cv-qualified) non-POD class type (or array thereof), the
object shall be default-initialized; if the object is of
const-qualified type, the underlying class type shall have a
user-declared default constructor. Otherwise, if no initializer is
specified for an object, the object and its subobjects, if any, have
an indeterminate initial value; if the object or any of its subobjects
are of const-qualified type, the program is ill-formed.
See Why does C++ require a user-provided default constructor to default-construct a const object?. Supposedly the program is ill-formed if the object is of const-qualified POD type, and there is no initializer specified (because POD are not default initialized). Note how g++ behaves for the following:
struct A {int a;};
struct B {int a = 1;};
int main()
{
A a;
B b;
const A c; // A is POD, error
const B d; // B is not POD, contains data member initializer, no error
}
Since C++17, this code is correct, as is the similar code from this question:
struct MyClass1 { int i{}; };
struct MyClass2 { const MyClass1 m; };
MyClass2 a;
clang 8.0.0 rejects this latter code even with -std=c++17 which means that clang 8.0.0 has a bug.
In C++17 the following new text was added as [dcl.init]/7 (as per P0490R0 in response to DR 253):
A class type T is const-default-constructible if default-initialization of T would invoke a user-provided constructor of T (not inherited from a base class) or if
each direct non-variant non-static data member M of T has a default member initializer or, if M is of class type X (or array thereof), X is const-default-constructible,
if T is a union with at least one non-static data member, exactly one variant member has a default member initializer,
if T is not a union, for each anonymous union member with at least one non-static data member, exactly one non-static data member has a default member initializer, and
each potentially constructed base class of T is const-default-constructible.
If a program calls for the default-initialization of an object of a const-qualified type T , T shall be a const-default-constructible class type or array thereof.
Prior to C++17 there was no such text; an object defined as const must either have an initializer or a user-provided constructor. So, prior to C++17, clang was correct and g++ was bugged to accept the code without diagnostic.
Please consider the following code:
template <typename T>
struct Test
{
Test() = default;
explicit Test(const T& arg)
: m_member(arg)
{
}
T m_member{};
};
int main()
{
Test<int> t1;
int v2 = 34;
Test<int&> t2(v2); // (!)
return 0;
}
Should the code above compile and exhibit no undefined behavior?
The line marked (!) instantiates the class template Test with a parameter of reference type. In this case, the default initializer for member Test::m_member is invalid (well, a reference must be initialized with some object). But on the other hand, the default constructor (the only one which could have used that default initializer) is never used in the program, and so it shouldn't be instantiated.
Under C++11, is the compiler allowed/required to attempt/skip the instantiation of a default initializer for a member which is not used in the instantiated constructors (i.e. the program doesn't instantiate any of the constructors which could require that initializer)?
Fortunately, this is OK.
But on the other hand, the default constructor (the only one which could have used that default initializer) is never used in the program, and so it shouldn't be instantiated.
Indeed:
[temp.mem.func]/2
The template-arguments for a member function of a class template are determined by the template-arguments of the type of the object for which the member function is called.
Since in your example, Test<int&>::Test() is not called, it's template-arguments are not determined and its construct doesn't make the program ill-formed.
This is described in CWG Issue 1396
Deferred instantiation and checking of non-static data member initializers
Section: 17.8.1 [temp.inst] Status: drafting Submitter: Jason Merrill Date: 2011-09-22
Non-static data member initializers get the same late parsing as
member functions and default arguments, but are they also instantiated
as needed like them? And when is their validity checked?
Notes from the October, 2012 meeting:
CWG agreed that non-static data member initializers should be handled
like default arguments.
As you see, the consensus was that default member initializers be treated like default arguments to functions. So we can examine the behavior for default arguments to determine how the CWG intends for this to be treated. We can see that those aren't instantiated along with the class definition:
[temp.inst]/1
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the
definitions or default arguments, of the class member functions,
member classes, scoped member enumerations, static data members and
member templates;
And that they would be instantiated if a constructor was called in a way that used them, same as a member function f in this paragraph
[temp.inst]/12
If a function template f is called in a way that requires a default
argument to be used, the dependent names are looked up, the semantics
constraints are checked, and the instantiation of any template used in
the default argument is done as if the default argument had been an
initializer used in a function template specialization with the same
scope, the same template parameters and the same access as that of the
function template f used at that point. This analysis is called
default argument instantiation. The instantiated default argument is
then used as the argument of f.
Should the code above compile and exhibit no undefined behavior?
Yes, I can't see any reason why it shouldn't.
Under C++11, is the compiler allowed/required to attempt/skip the
instantiation of a default initializer for a member which is not used
in the instantiated constructors (i.e. the program doesn't instantiate
any of the constructors which could require that initializer)?
I don't have the C++11 spec but the current draft says:
[default.ctor-2.3]
A defaulted default constructor for class X is defined as deleted if:
any non-static data member with no default member initializer ([class.mem]) is of reference type,
Since you do have a default member initializer (T m_member{};) it will not delete your defaulted default constructor in case you try to use it when T is a reference type.
It'll instead fail compiling:
All output is from using -std=c++11
g++: error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’
clang++ error: non-const lvalue reference to type 'int' cannot bind to an initializer list temporary
Trying to fix it by removing the default member initializer:
11 | T m_member;
Results in the deletion required:
21:16: error: use of deleted function ‘Test<T>::Test() [with T = int&]’
21 | Test<int&> x;
| ^
4:5: note: ‘Test<T>::Test() [with T = int&]’ is implicitly deleted because the default definition would be ill-formed:
4 | Test() = default;
| ^~~~
4:5: error: uninitialized reference member in ‘struct Test<int&>’
I want to implement my own pointer (with few helper methods) extended from shared_ptr.
class Event;
class EventPtr : public std::shared_ptr<Event> {
public:
constexpr EventPtr()
: std::shared_ptr<Event>() {
}
constexpr EventPtr(std::nullptr_t p)
: std::shared_ptr<Event>(p) {
}
explicit EventPtr(Event* ptr)
: std::shared_ptr<Event>(ptr) {
}
};
The problem is that compiler gives me the following error for both constexpr constructors:
constexpr constructor never produces constant expression
Tell me please how to fix it.
The rules on constexpr constructors changed between C++11 and C++14; see DR1911 constexpr constructor with non-literal base class and this bug.
The fix is to compile in C++14 mode (-std=c++14).
Language in C++11 [dcl.constexpr]:
For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required. For a constexpr constructor, if no argument values exist such that after function invocation substitution, every
constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required.
Under C++11, shared_ptr can have constexpr constructors but any class type inheriting from shared_ptr or with a shared_ptr member cannot, because shared_ptr is not a literal type (it has a destructor) and so cannot appear in a constant expression. For C++14 this was simplified to:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.
Unfortunately this makes all constexpr constructors of non-literal types undefined behavior; DR1911 fixed this by adding a subclause (bolded below):
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.20), or, for a constructor, a constant initializer for some object (3.6.2), the program is ill-formed; no diagnostic required.
struct X { ~X() {} constexpr X() {} }; // OK in C++11, UB in C++14, OK since DR1911
struct Y : X { constexpr Y() : X() {} }; // UB in C++11, UB in C++14, OK since DR1911