This question already has answers here:
What changes to C++ made copy initialization work for class with explicit constructor?
(2 answers)
Closed 5 months ago.
I've got more confused when I see this question: Is a class instantiation--class_name() a xvalue or a prvalue? I'm trying to understand what does it mean by a class prvalue and a class xvalue. Someone tell me that they're called value category. But I think it would be better if I provide an example, because I'm very confused.
class myclass { public: myclass() {}; };
void myfunc(myclass c1){ }
int main(void)
{
myfunc(myclass());
}
So what's myclass()? Is it a prvalue or xvalue?
I need a rule from the standard so that I wouldn't ask more questions.
The expression myclass() is explicit type conversion using functional notation, per [expr.type.conv]/1:
A simple-type-specifier or typename-specifier followed by a
parenthesized optional expression-list or by a braced-init-list (the
initializer) constructs a value of the specified type given the
initializer [..]
Here, the simple-type-specifier myclass is followed by parenthesized expression-list () with an empty initializer-list. This expression constructs a value of type myclass from the empty initializer-list.
So you might ask, Is that constructed value is prvalue or xvalue or lvalue. So here we've to invoke the immediately next paragraph: [expr.type.conv]/2:
If the initializer is a parenthesized single expression, the type
conversion expression is equivalent to the corresponding cast
expression. Otherwise, if the type is cv void and the initializer is
() or {} (after pack expansion, if any), the expression is a prvalue
of the specified type that performs no initialization. Otherwise, the
expression is a prvalue of the specified type whose result object is
direct-initialized with the initializer.
Our initializer is empty initializer-list, and the type is not cv void, so we've ended with the sentence that says:
Otherwise, the expression is a prvalue of the specified type whose
result object is direct-initialized with the initializer.
Hence, the expression myclass() is a prvalue, whose result object is direct-initialized, from the empty initializer-list, by calling the default constructor.
In the following program the object A a is directly initialized from braced-init-list {A{}}:
#include <iostream>
struct A {
int v = 0;
A() {}
A(const A &) : v(1) {}
};
int main() {
A a({A{}});
std::cout << a.v;
}
MSVC and GCC print 0 here meaning that copy-elision takes place. And Clang prints 1 executing the copy-constructor.
Online demo: https://gcc.godbolt.org/z/1vqvf148z
Which compiler is right here?
Which compiler is right here?
I think that clang is right in using the copy constructor and printing 1 for the reason(s) explained below.
First note that A a({A{}}); is direct-initialization as can be seen from dcl.init#16.1:
The initialization that occurs:
16.1) for an initializer that is a parenthesized expression-list or a braced-init-list,
16.2) for a new-initializer,
16.3) in a static_cast expression ([expr.static.cast]),
Now, dcl.init#17.6 is applicable here:
17.6) Otherwise, if the destination type is a (possibly cv-qualified) class type:
17.6.1) If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.
[ Example: T x = T(T(T())); calls the T default constructor to initialize x.
— end example
]
17.6.2) Otherwise, 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 ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
Then:
17.6.2.1) If overload resolution is successful, the selected constructor is called to initialize the object, with the initializer expression or expression-list as its argument(s).
(emphasis mine)
This means that the copy constructor(which is the selected constructor here) will be used/called to initialize the object named a with the expression-list as its argument and since in your copy ctor's member initializer list you're initializing a.v to 1, the output printing 1 of clang is correct.
int main(){
void{1,2}; // #1
}
#1 is complained be invalid by both GCC and Clang. However, from the point of grammar, it may be valid.
expr.post#general-1
simple-type-specifier braced-init-list
The grammar does not restrict what the braced-init-list should be. From the point of the relevant rule, it also does not have any statement for this case
expr.type.conv#2
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of type void that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
From the point of definition of the initialization, it states that
dcl.init#general-14
The initialization that occurs
in a functional notation type conversion
Since a prvalue of type void performs no initialization and does not have a result object, hence the whole dcl.init.list#3 is not suitable here
List-initialization of an object or reference of type T is defined as follows:
The case cannot even be caught by dcl.init.list#3.12
Otherwise, the program is ill-formed.
So, I wonder Is any rule states that void{1,2} should be considered ill-formed?
Generally speaking, parentheses and braces are very different. For minimal reproducible example:
#include <array>
#include <vector>
int main()
{
std::array<int, 2>{42, 42}; // OK
std::array<int, 2>(42, 42); // ill-formed
std::vector<int>{42, 42}; // two elements
std::vector<int>(42, 42); // 42 elements
}
However, since empty braces use value-initialization instead of std::initializer_list constructors, is there any different between empty parentheses and empty braces when used as initializers?
More formally, given a type T, is it possible that T() and T{} are different? (Either may be ill-formed.)
(This question and answer was originally created for C++20 standard compatible vector on Code Review Stack Exchange. It is intended that the answer covers all possible cases. Please inform me if I missed any.)
(The links in this answer point to N4659, the C++17 final draft. However, at the time of this writing, the situation is exactly the same for C++20.)
Yes, it's possible. There are two cases:
Case 1
T is a non-union aggregate for which zero-initialization, followed by default-initialization if the aggregate has a non-trivial constructor, differs from copy-initialization from {}.
We can use std::in_place_t to construct our example, because it has an explicit default constructor. Minimal reproducible example:
#include <utility>
struct A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(live demo)
Case 1, variant
T is a union aggregate for whose first element default-initialization differs from copy-initialization from {}.
We can change struct to union in Case 1 to form a minimal reproducible example:
#include <utility>
union A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(live demo)
Case 2
T is of the form const U& or U&& where U can be list-initialized from {}.
Minimal reproducible example:
int main()
{
using R = const int&;
R{}; // well-formed
R(); // ill-formed
}
(live demo)
Detailed explanation
T()
Per [dcl.init]/17:
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 or is = braced-init-list, the object or reference is list-initialized.
If the destination type is a reference type, see [dcl.init.ref].
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 [dcl.init.string].
If the initializer is (), the object is value-initialized.
[...]
We can conclude that T() always value-initializes the object.
T{}
Per [dcl.init]/17:
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 or is = braced-init-list, the object or reference is
list-initialized.
[...]
That's enough for us to conclude that T{} always list-initializes the object.
Now let's go through [dcl.init.list]/3. I have highlighted the possible cases. The other cases are not possible because they require the initializer list to be non-empty.
List-initialization of an object or reference of type T is defined
as follows:
(3.1) If T is an aggregate class and the initializer list has a single element of type cv U, where U is T or a class derived from
T, the object is initialized from that element (by
copy-initialization for copy-list-initialization, or by
direct-initialization for direct-list-initialization).
(3.2) Otherwise, if T is a character array and the initializer list has a
single element that is an appropriately-typed string literal
([dcl.init.string]), initialization is performed as described in that
section.
(3.3) Otherwise, if T is an aggregate, aggregate initialization is
performed.
(3.4) Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is
value-initialized.
(3.5) Otherwise, if T is a specialization of
std::initializer_list<E>, the object is constructed as described
below.
(3.6) Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best
one is chosen through overload resolution ([over.match],
[over.match.list]). If a narrowing conversion (see below) is required
to convert any of the arguments, the program is ill-formed.
(3.7) Otherwise, if T is an enumeration with a fixed underlying type
([dcl.enum]), the initializer-list has a single element v, and the
initialization is direct-list-initialization, the object is
initialized with the value T(v) ([expr.type.conv]); if a narrowing
conversion is required to convert v to the underlying type of T,
the program is ill-formed.
(3.8) 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 (by copy-initialization for copy-list-initialization, or
by direct-initialization for direct-list-initialization); if a
narrowing conversion (see below) is required to convert the element to
T, the program is ill-formed.
(3.9) Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result
object by copy-list-initialization or direct-list-initialization,
depending on the kind of initialization for the reference. The prvalue
is then used to direct-initialize the reference.
(3.10) Otherwise, if the initializer list has no elements, the object is value-initialized.
(3.11) Otherwise, the program is ill-formed.
(Note: (3.6) is not possible in this case, for the following reason: (3.4) covers the case where a default constructor is present. In order for (3.6) to be considered, a non-default constructor has to be called, which is not possible with an empty initializer list. (3.11) is not possible because (3.10) covers all cases.)
Now let's analyze the cases:
(3.3)
For an aggregate, value-initialization first performs zero-initialization and then, if the element has a non-trivial default constructor, default-initialization, on the aggregate, per [dcl.init]/8:
To value-initialize an object of type T means:
[...]
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is
zero-initialized and the semantic constraints for
default-initialization are checked, and if T has a non-trivial default
constructor, the object is default-initialized;
[...]
Non-union aggregates
When copy-initializing a non-union aggregate from {}, elements that are not explicitly initialized with a default member initializer are copy-initialized from {} per [dcl.init.aggr]/8:
If there are fewer initializer-clauses in the list than there are
elements in a non-union aggregate, then each element not explicitly
initialized is initialized as follows:
If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
Otherwise, the program is ill-formed.
[...]
See Case 1.
Union aggregates
If the aggregate is a union, and no member has a default member initializer, then copying-initializing the aggregate from {} copy-initializes the first element from {}: [dcl.init.aggr]/8:
[...]
If the aggregate is a union and the initializer list is empty, then
if any variant member has a default member initializer, that member is initialized from its default member initializer;
otherwise, the first member of the union (if any) is copy-initialized from an empty initializer list.
See Case 1, variant.
(3.4)
Value-initialized, so no difference.
(3.9)
T() isn't allowed if T is a reference per [dcl.init]/9:
A program that calls for default-initialization or
value-initialization of an entity of reference type is ill-formed.
See Case 2.
(3.10)
Similarly, value-initialized. No difference.
is there any different between empty parentheses and empty braces when used as initializers
There are cases where empty parentheses cannot be used as initialiser because it would be syntactically ambiguous with a function declaration:
T t(); // function declaration; not initialisation
T t{}; // value initialisation
More formally, given a type T, is it possible that T() and T{} are different?
The ambiguity described above has a case where T() is parsed as pointer to function, known as the Most Vexing Parse:
U t(T()); // function declaration; not initialisation
U t(T{}); // value initialisation, and direct initialisation
Say I want to refer to a member of an initializer_list that I already defined. Can I do it?
This code compiles and gives the expected: "13 55 " in both Visual Studio and gcc, I'd just like to know that it's legal:
const int foo[2] = {13, foo[0] + 42};
So what we have here is aggregate initialization covered in section 8.5.1 of the draft C++ standard and it says:
An aggregate is an array or a class [...]
and:
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 [...]
Although it seems reasonable that side effects from initializing each member of the aggregate should be sequenced before the next, since each element in the initializer list is a full expression. The standard does not actually guarantee this we can see this from defect report 1343 which says:
The current wording does not indicate that initialization of a non-class object is a full-expression, but presumably should do so.
and also notes:
Aggregate initialization could also involve more than one full-expression, so the limitation above to “initialization of a non-class object” is not correct.
and we can see from a related std-discussion topic Richard Smith says:
[intro.execution]p10: "A full-expression is an expression that is not
a subexpression of another expression. [...] If a language construct
is defined to produce an implicit call of a function, a use of the
language construct is considered to be an expression for the purposes
of this definition."
Since a braced-init-list is not an expression, and in this case it
does not result in a function call, 5 and s.i are separate
full-expressions. Then:
[intro.execution]p14: "Every value computation and side effect
associated with a full-expression is sequenced before every value
computation and side effect associated with the next full-expression
to be evaluated."
So the only question is, is the side-effect of initializing s.i
"associated with" the evaluation of the full-expression "5"? I think
the only reasonable assumption is that it is: if 5 were initializing a
member of class type, the constructor call would obviously be part of
the full-expression by the definition in [intro.execution]p10, so it
is natural to assume that the same is true for scalar types.
However, I don't think the standard actually explicitly says this
anywhere.
So this is currently not specified by the standard and can not be relied upon, although I would be surprised if an implementation did not treat it the way you expect.
For a simple case like this something similar to this seems a better alternative:
constexpr int value = 13 ;
const int foo[2] = {value, value+42};
Changes In C++17
The proposal P0507R0: Core Issue 1343: Sequencing of non-class initialization clarifies the full-expression point brought up here but does not answer the question about whether the side-effect of initialization is included in the evaluation of the full-expression. So it does not change that this is unspecified.
The relevant changes for this question are in [intro.execution]:
A constituent expression is defined as follows:
(9.1) — The constituent expression of an expression is that expression.
(9.2) — The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the
constituent expressions of the elements of the respective list.
(9.3) — The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the
constituent expressions of the initializer-clause.
[ Example:
struct A { int x; };
struct B { int y; struct A a; };
B b = { 5, { 1+1 } };
The constituent expressions of the initializer used for the initialization of b are 5 and 1+1. —end example ]
and [intro.execution]p12:
A full-expression is
(12.1) — an unevaluated operand (Clause 8),
(12.2) — a constant-expression (8.20),
(12.3) — an init-declarator (Clause 11) or a mem-initializer (15.6.2), including the constituent expressions of the
initializer,
(12.4) — an invocation of a destructor generated at the end of the lifetime of an object other than a temporary
object (15.2), or
(12.5) — an expression that is not a subexpression of another expression and that is not otherwise part of a
full-expression.
So in this case both 13 and foo[0] + 42 are constituent expression which are part of a full-expression. This is a break from the analysis here which posited that they would each be their own full-expressions.
Changes In C++20
The Designated Initialization proposal: P0329 contains the following addition which seems to make this well defined:
Add a new paragraph to 11.6.1 [dcl.init.aggr]:
The initializations of the elements of the aggregate are evaluated in the element order. That is,
all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order.
We can see this is reflected in the latest draft standard.