Must consteval constructor initialize all data members? - c++

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

Related

`default` constructor with uninitialized member in constant expression

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.

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.

Constexpr conditions for constructor

On this site, it is specified that:
"A constexpr function must satisfy the following requirements:
[...]
there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression (for constructors, use in a constant initializer is sufficient) (since C++14). No diagnostic is required for a violation of this bullet."
What is the meaning of the bolded statement?
Looking at the linked defect report
struct X {
std::unique_ptr<int> p;
constexpr X() { }
};
Before C++14, this would be ill-formed due to [dcl.constexpr]
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.
Which mandates that there exists some argument (in this case, only the empty set) that can create a constant expression for the invocation of X::X, as in
constexpr X x; // must be valid before C++14
Since std::unique_ptr isn't a literal type, it has a non-trivial destructor, this is impossible. Yet the defect report proposed that constexpr constructors should still be well-formed in such cases due to this kind of use case
X x; // not constexpr, but initialization should be constant
Hence the rewording
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, a constant initializer for some object , the program is ill-formed, no diagnostic required.
Translated, it means: a constexpr constructor is well-formed as long as it is a constexpr function, and its member initializations are also constexpr functions, even if the type itself can never be constexpr.

Trying to understand [expr.const] in C++14

Where in the C++14 Standard, does it prohibit the declaration of object a below?
class A{ int i = 1; public: A():i{1}{} };
int main()
{
constexpr A a{};
}
See live example
Note that I highlighted the word declaration, because I don't think bullet points (2.7.2) or (2.7.3), in §5.19[expr.const]p2 is an answer for the question.
[dcl.constexpr]p9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). [...]
The error you're getting now is because your type is not a literal type. Your type is not a literal type because it does have a custom constructor, but doesn't have any constexpr constructor. The wording in the error message is rather clear about the exact requirements.
If you add a constexpr constructor (but not the default constructor), the error message changes:
class A{ int i = 1; public: A():i{1}{} constexpr A(int){} };
int main()
{
constexpr A a{};
}
Now the error message becomes
error: call to non-constexpr function ‘A::A()’
constexpr A a{};
This is the second part I bolded: it's not the initialiser that has to be a constant expression. You're right, your initialiser isn't an expression at all. It's the constructor call that must be a constant expression, and although it isn't expressed explicitly in the source code, it is an expression nonetheless. This is covered in [expr.const] rather clearly:
an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor (12.4) [...]
to which you already refer in your question.
Well, your default constructor is not constexpr. Therefore, you cannot create a default constructed constexpr object.