`default` constructor with uninitialized member in constant expression - c++

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.

Related

Must consteval constructor initialize all data members?

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).

Can an object of class type T be constant initialized when T has a non-trivial destructor?

Let's look at this sample of code:
class D
{
public:
constexpr D(int val) : i(val) { };
~D() { };
private:
int i;
};
D d(3);
According to the documentation, D should be constant initialized:
Only the following variables are constant initialized: [...]
2. Static or thread-local object of class type that is initialized by a
constructor call, if the constructor is constexpr and all constructor
arguments (including implicit conversions) are constant expressions,
and if the initializers in the constructor's initializer list and the
brace-or-equal initializers of the class members only contain constant
expressions.
Indeed, d is initialized by constructor call, the constructor of D is constexpr and my argument (3) is a constant expression.
However, to specify to the compiler the value of a variable can be evaluated at compile time, it is possible to use constexpr specifier. But, in this case, it won't compile because D is not a LiteralType because it define a non-trivial constructor.
So, in my snippet, is d really constant initialized? If so, why can't I use constexpr specifier?
So, in my snippet, is d really constant initialized? If so, why can't I use constexpr specifier?
Yes, it will be constant initialized. As you've quoted, constant initialization doesn't need the type to be a LiteralType. But constexpr does need it. Your type is not a LiteralType, so it cannot be a constexpr. But the type and constructor call fulfills the requirements of being constant initialization.
Btw., C++20 will have constinit. With this, you can make sure that a variable gets static initialized (which means constant initialization in your case).
You can check out constinit for your example on godbolt, as a further evidence that it compiles successfully, and you can see that the object is initialized at compile-time (not a requirement by the standard, but GCC does it).

Should an explicitly defaulted constexpr ctor allow non-constexpr initialization

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.

Why doesn't my compiler recognise "Bond() = default;"?

Please look at this code
class Bond
{
public:
Bond(int payments_per_year, int period_lengths_in_months);
Bond() = default;
private:
const int payments_per_year;
const int period_length_in_months;
};
int main()
{
Bond b; // Error here
}
When attempting to compile I get an error:
error C2280: 'Bond::Bond(void)': attempting to reference a deleted function".
It's not a "rule of 3" violation since I've added the default constructor back.
Why doesn't the compiler recognise Bond() = default;?
The default constructor is suppressed since there are constant members that need to be explicitly initialised.
Therefore, due to that suppression, writing Bond() = default does not reintroduce the default constructor.
(You can see this effect by removing all the constructors in the class - you still can't instantiate a b.)
If you drop the const from the members then all will be well; although another alternative is to supply a brace-or-equal-initializer for each const member;
const int payments_per_year = 2;
const int period_length_in_months = 6;
for example.
You are being affected by section [class.default.ctor]p2 of the draft C++ standard (or [class.ctor]p5 in C++11) which says:
A defaulted default constructor for class X is defined as deleted if:
...
- any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer does not have a user-provided default constructor,
...
They possible key to fixing your issue is with the phrase with no brace-or-equal-initializer so if you provide brace-or-equal-initializer that will fix your issue e.g.:
const int payments_per_year{12};
const int period_length_in_months{48};
brace-or-equal-initializer does not require braces, we can see this the grammar:
brace-or-equal-initializer:
= initializer-clause
braced-init-list
but using uniform initialization has some advantages such as making narrowing conversions ill-formed that it is worth getting used to using them.
Both gcc and clang provide more meaningful diagnostics for this see the live godbolt session. Sometimes it can be helpful to try your code on multiple compilers, especially if you have a minimal test case like this e.g. clang says:
warning: explicitly defaulted default constructor is implicitly deleted [-Wdefaulted-function-deleted]
Bond() = default;
^
note: default constructor of 'Bond' is implicitly deleted because field 'payments_per_year' of const-qualified type 'const int' would not be initialized
const int payments_per_year;
^
...
Another fix, is to specify a default value in the declaration of the constants:
const int payments_per_year = {12};
This can still be overridden by the valued constructor, but allows the default constructor to proceed.
This is also a very flexible way to simplify your multiple constructor cases.

why is an uninitialized constexpr variable not constant?

I'm not sure if this is a compiler bug or if I misunderstand constexpr:
struct S{};
constexpr S s1{};
constexpr S s2;
struct test{
static constexpr auto t1 = s1;
static constexpr auto t2 = s2; //error here
};
GCC 4.8 is giving me an odd error "error: field initializer is not constant". Is s2 really not a constant? If so why?
For clarity I actually am using a bunch of empty structs in my code (for meta programming https://github.com/porkybrain/Kvasir) so I really am interested in this specific example.
Update: The code should compile, because [class.ctor]/5 reads:
The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (12.6.2) and an empty compound-statement. If that user-written default constructor would satisfy the requirements of a constexpr constructor (7.1.5), the implicitly-defined default constructor is constexpr.
And since S is just an empty struct, the implicitly defined default constructor is empty and thus satisfying constexpr requirements.
So here you are dealing with imperfection of the compilers, which you have to workaround somehow.
Old answer:
Clang emits more sensible error message:
main.cpp:3:13: error: default initialization of an object of const type 'const S'
requires a user-provided default constructor
constexpr S s2;
^
[dcl.constexpr]/9 provides the explanation and even almost exactly your code as an example:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized.(...)
[ Example:
struct pixel {
int x, y;
};
constexpr pixel ur = { 1294, 1024 };// OK
constexpr pixel origin; // error: initializer missing
—end example ]