This code compiles just fine on all big 4 compilers, even on -pedantic
struct S
{
constexpr S(int a) {}
};
constexpr int f(S a)
{
return 1;
}
int main()
{
int a = 0;
S s(a);
constexpr int b = f(s);
}
However, this shouldn't be so according to the standard... right? Firstly, s wouldn't be usable in constant expressions [expr.const]/3, because it fails to meet the criteria of being either constexpr, or, const and of enum or integral type.
Secondly, it is not constant-initialized [expr.const]/2 because the full expression of the initialization would not be a constant expression [expr.const]/10 due to a lvalue-to-rvalue conversion being performed on a variable (a) that is not usable in constant expressions when initializing the parameter of the constructor.
Are all these compilers just eliding the initialization of the parameter of the constructor because it has no side-effects, and is it standard conforming (I'm 99% sure it isn't, as the only way for it to so would be to make s constexpr, and to pass it either a const int or a int that is declared constexpr)?
I believe the magician's trick here is the copy c'tor of S. You omitted it, so a defaulted one is generated for you here. Now it's a constexpr function too.
[class.copy.ctor] (emphasis mine)
12 A copy/move constructor that is defaulted and not defined as
deleted is implicitly defined when it is odr-used ([basic.def.odr]),
when it is needed for constant evaluation ([expr.const]), or when it
is explicitly defaulted after its first declaration. [ Note: The
copy/move constructor is implicitly defined even if the implementation
elided its odr-use ([basic.def.odr], [class.temporary]). — end note ]
If the implicitly-defined constructor would satisfy the requirements
of a constexpr constructor ([dcl.constexpr]), the implicitly-defined
constructor is constexpr.
Does the evaluation of the copy c'tor run afoul of any of the points in [expr.const]/4? It does not. It doesn't perform an lvalue to rvalue conversion on any of the argument's members (there are none to perform the conversion on). It doesn't use its reference parameter in any way that will require said reference to be usable in a constant expression. So we indeed get a valid constant expression, albeit a non-intuitive one.
We can verify the above by just adding a member to S.
struct S
{
int a = 1;
constexpr S(int a) {}
};
Now the copy c'tor is trying to access an object that is not usable in a constant expression as part of its evaluation (via said reference). So indeed, compilers will complain.
Related
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).
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.
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.
I have a class with a constexpr value constructor, but no copy or move ctor
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
};
int main() {
constexpr C arr[] = {1, 2};
}
I've found that this code doesn't work because it's actually trying to use the move constructor for C rather than the value constructor to construct in place. One issue is that I want this object to be unmovable (for test purposes) but I thought "okay, fine, I'll add a move constructor."
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
C& operator=(C&&) = delete;
C(C&&) { /*something*/ } // added, assume this must be non trivial
};
Okay fine, now it uses the move constructor and everything works under gcc but when I use clang, it complains because the move constructor is not marked constexpr
error: constexpr variable 'arr' must be initialized by a constant expression
constexpr C arr[] = {1, 2};
If I mark the move constructor constexpr it works under gcc and clang, but the issue is that I want to have code in the move constructor if it runs at all, and constexpr constructors must have empty bodies. (The reason for my having code in the move ctor isn't worth getting into).
So who is right here? My inclination is that clang would be correct for rejecting the code.
NOTE
It does compile with initializer lists and non-copyable non-movable objects as below:
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
C& operator=(C&&) = delete;
C(C&&) = delete;
};
int main() {
constexpr C arr[] = {{1}, {2}};
}
My main concern is which compiler above is correct.
So who is right here?
Clang is correct in rejecting the code. [expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
(1.9), would evaluate one of the following expressions:
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)
Clearly your move constructor isn't a constexpr constructor - [dcl.constexpr]/2
Similarly, a constexpr specifier used in a constructor declaration
declares that constructor to be a constexpr constructor.
And the requirements for an initializer of a constexpr object are in [dcl.constexpr]/9:
[…] every full-expression that appears in its initializer shall be a
constant expression. [ Note: Each implicit conversion used in
converting the initializer expressions and each constructor call used
for the initialization is part of such a full-expression. — end note
]
Finally the move constructor is invoked by the copy-initialization of the array elements with the corresponding initializer-clauses - [dcl.init]:
Otherwise (i.e., for the remaining copy-initialization cases),
user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the
call initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization.
In the second example, copy-list-initialization applies - and no temporary is introduced.
By the way: GCC 4.9 does not compile the above, even without any warning flags provided.
§8.5 [dcl.init]/p17:
The semantics of initializers are as follows. The destination type is
the type of the object or reference being initialized and the source
type is the type of the initializer expression. If the initializer is
not a single (possibly parenthesized) expression, the source type is
not defined.
If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
[...]
If the destination type is a (possibly cv-qualified) class type:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, [...]
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
[...]
§8.5.1 [dcl.init.aggr]/p2:
When an aggregate is initialized by an initializer list, as specified
in 8.5.4, the elements of the initializer list are taken as
initializers for the members of the aggregate, in increasing subscript
or member order. Each member is copy-initialized from the
corresponding initializer-clause. If the initializer-clause is an
expression and a narrowing conversion (8.5.4) is required to convert
the expression, the program is ill-formed. [ Note: If an
initializer-clause is itself an initializer list, the member is
list-initialized, which will result in a recursive application of the
rules in this section if the member is an aggregate. —end note ]
§8.5.4 [dcl.init.list]/p3:
List-initialization of an object or reference of type T is defined as
follows:
If T is an aggregate, aggregate initialization is performed (8.5.1).
[...]
Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen
through overload resolution (13.3, 13.3.1.7). If a narrowing
conversion (see below) is required to convert any of the arguments,
the program is ill-formed.
[...]
For constexpr C arr[] = {1, 2};, aggregate initialization copy-initializes each element from the corresponding initializer-clause, i.e., 1 and 2. As described in §8.5 [dcl.init]/p17, this constructs a temporary C and then direct-initializes the array element from the temporary, which requires an accessible copy or move constructor. (The copy/move can be elided, but the constructor must still be available.)
For constexpr C arr[] = {{1}, {2}};, the elements are copy-list-initialized instead, which does not construct temporaries (note the absence of any mention of a temporary being constructed in §8.5.4 [dcl.init.list]/p3).
Except for supporting multiple arguments, disallowing narrowing conversion, matching constructor taking std::initializer_list argument, what else is different for copy-list-initialization against traditional copy-initialization?
To be specific, assume there are two user-defined types, A and B:
class A {...};
class B {...};
B b;
A a1 = {b};
A a2 = b;
What kind of definition of A and B will make a difference on those two forms of initialization? e.g. Is there a certain definition of A and B that will make one of the initialization legal but the other illegal, or both legal but with different semantics, or both illegal with different causes?
(Assume A doesn't have a constructor taking std::initializer_list argument.)
EDIT: Adding a link to a somewhat related question of mine: What is the supposed behavior of copy-list-initialization in the case of an initializer with a conversion operator?
Copy-initialization always considers availability of copy constructors, while copy-list-initialization doesn't.
class B {};
struct A
{
A(B const&) {}
A(A const&) = delete;
};
B b;
A a1 = {b}; // this compiles
A a2 = b; // this doesn't because of deleted copy-ctor
This is because copy-list-initialization is identical to direct-list-initialization except in one situation - had A(B const&) been explicit, the former would've failed, while the latter will work.
class B {};
struct A
{
explicit A(B const&) {}
};
int main()
{
B b;
A a1{b}; // compiles
A a2 = {b}; // doesn't compile because ctor is explicit
}
Probably, the behaviour of the new copy-list-initialization was defined to be "good" and consistent, but the "weird" behaviour of old copy-initialization couldn't be changed because of backward compatibility.
As you can see the rules for list-initialization in this clause are identical for direct and copy forms.
The difference related to explicit is described only in the chapter on overload resolution. But for traditional initialization direct and copy forms are not identical.
The traditional and brace initializations are defined separately, so there's always a potential for some (probably unintended) subtle differences.
The differences I can see from the excerpts of the standard:
1. Already mentioned differences
narrowing conversions are disallowed
multiple arguments are possible
braced syntax prefers initializer-list constructors if they present:
struct A
{
A(int i_) : i (i_) {}
A(std::initializer_list<int> il) : i (*il.begin() + 1) {}
int i;
}
A a1 = 5; // a1.i == 5
A a2 = {5}; // a2.i = 6
2. Different behaviour for aggregates
For aggregates you can't use braced copy-constructor, but can use traditional one.
struct Aggr
{
int i;
};
Aggr aggr;
Aggr aggr1 = aggr; // OK
Aggr aggr2 = {aggr}; // ill-formed
3. Different behaviour for reference initialization in presence of conversion operator
Brace initialization can't use operators of conversion to reference type
struct S
{
operator int&() { return some_global_int;}
};
int& iref1 = s; // OK
int& iref2 = {s}; // ill-formed
4. Some subtle differences in initialization of object of class type by object of other type
These difference are marked by [*] in the excerpts of the Standard at the end of this answer.
Old initialization uses notion of user-defined conversion sequences (and, particularly, requires availability of copy constructor, as was mentioned)
Brace initialization just performs overload resolution among applicable constructors, i.e. brace initialization can't use operators of conversion to class type
These differences are responsible for some not very obvious (for me) cases like
struct Intermediate {};
struct S
{
operator Intermediate() { return {}; }
operator int() { return 10; }
};
struct S1
{
S1(Intermediate) {}
};
S s;
Intermediate im1 = s; // OK
Intermediate im2 = {s}; // ill-formed
S1 s11 = s; // ill-formed
S1 s12 = {s}; // OK
// note: but brace initialization can use operator of conversion to int
int i1 = s; // OK
int i2 = {s}; // OK
5. Difference in overload resolution
Different treatment of explicit constructors
See 13.3.1.7 Initialization by list-initialization
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations (13.3.1.3, 13.3.1.4), where only converting constructors
are considered for copy initialization. This restriction only applies
if this initialization is part of the final result of overload
resolution. — end note ]
If you can see more differences or somehow correct my answer (including grammar mistakes), please do.
Here are the relevant (but long) excerpts from the current draft of the C++ standard (I haven't found a way to hide them under spoiler):
All of them are located in the chapter 8.5 Initializers
8.5 Initializers
If the initializer is a (non-parenthesized) braced-init-list, the
object or reference is list-initialized (8.5.4).
If the destination type is a reference type, see 8.5.3.
If the destination type is an array of characters, an array of char16_t, an
array of char32_t, or an array of wchar_t, and the initializer is a
string literal, see 8.5.2.
If the initializer is (), the object is
value-initialized.
Otherwise, if the destination type is an array,
the program is ill-formed.
If the destination type is a (possibly
cv-qualified) class type:
If the initialization is
direct-initialization, or if it is copy-initialization where the
cv-unqualified version of the source type is the same class as, or a
derived class of, the class of the destination, constructors are
considered. The applicable constructors are enumerated (13.3.1.3), and
the best one is chosen through overload resolution (13.3). The
constructor so selected is called to initialize the object, with the
initializer expression or expression-list as its argument(s). If no
constructor applies, or the overload resolution is ambiguous, the
initialization is ill-formed.
[*] Otherwise (i.e., for the
remaining copy-initialization cases), user-defined conversion
sequences that can convert from the source type to the destination
type or (when a conversion function is used) to a derived class
thereof are enumerated as described in 13.3.1.4, and the best one is
chosen through overload resolution (13.3). If the conversion cannot be
done or is ambiguous, the initialization is ill-formed. The function
selected is called with the initializer expression as its argument; if
the function is a constructor, the call initializes a temporary of the
cv-unqualified version of the destination type. The temporary is a
prvalue. The result of the call (which is the temporary for the
constructor case) is then used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
Otherwise, if the source type is a
(possibly cv-qualified) class type, conversion functions are
considered. The applicable conversion functions are enumerated
(13.3.1.5), and the best one is chosen through overload resolution
(13.3). The user-defined conversion so selected is called to convert
the initializer expression into the object being initialized. If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed.
Otherwise, the initial value of the object being
initialized is the (possibly converted) value of the initializer
expression. Standard conversions (Clause 4) will be used, if
necessary, to convert the initializer expression to the cv-unqualified
version of the destination type; no user-defined conversions are
considered. If the conversion cannot be done, the initialization is
ill-formed.
8.5.3 References ...
8.5.4 List-initialization
List-initialization of an object or reference of type T is defined as
follows:
If T is an aggregate, aggregate initialization is
performed (8.5.1).
Otherwise, if the initializer list has no
elements and T is a class type with a default constructor, the object
is value-initialized.
Otherwise, if T is a specialization of
std::initializer_list<E>, a prvalue initializer_list object is
constructed as described below and used to initialize the object
according to the rules for initialization of an object from a class of
the same type (8.5).
[*] Otherwise, if T is a class type,
constructors are considered. The applicable constructors are
enumerated and the best one is chosen through overload resolution
(13.3, 13.3.1.7). If a narrowing conversion (see below) is required to
convert any of the arguments, the program is ill-formed.
Otherwise, if the initializer list has a single element of type E and
either T is not a reference type or its referenced type is
reference-related to E, the object or reference is initialized from
that element; if a narrowing conversion (see below) is required to
convert the element to T, the program is ill-formed.
Otherwise, if
T is a reference type, a prvalue temporary of the type referenced by T
is copy-list-initialized or direct-list-initialized, depending on the
kind of initialization for the reference, and the reference is bound
to that temporary. [ Note: As usual, the binding will fail and the
program is ill-formed if the reference type is an lvalue reference to
a non-const type. — end note ]
Otherwise, if the initializer list
has no elements, the object is value-initialized.
Otherwise, the program is ill-formed.