struct B {
B() throw();
B(const B&) = default; // implicit exception specification is noexcept(true)
B(B&&, int = (throw Y(), 0)) noexcept;
~B() noexcept(false);
};
int n = 7;
struct D : public A, public B {
int * p = new int[n];
// D::D() potentially-throwing, as the new operator may throw bad_alloc or bad_array_new_length
// D::D(const D&) non-throwing
// D::D(D&&) potentially-throwing, as the default argument for B's constructor may throw
// D:: D() potentially-throwing
};
Consider the above code which is cited from except.spec#11. I have no doubt to the exception specification of all constructors of D except for D::D(D&&), which obeys the following rule:
An implicitly-declared constructor for a class X, or a constructor without a noexcept-specifier that is defaulted on its first declaration, has a potentially-throwing exception specification if and only if any of the following constructs is potentially-throwing:
a constructor selected by overload resolution in the implicit definition of the constructor for class X to initialize a potentially constructed subobject, or
a subexpression of such an initialization, such as a default argument expression, or,
for a default constructor, a default member initializer.
Obviously, The rule to make D::D(D&&) have a potentially-throwing exception specification is neither the first bullet nor the third bullet. For the first rule, the selected constructor to initialize base subobject of type B is B(B&&, int = (throw Y(), 0)) noexcept, which is declared to have a non-throwing exception specification. The third rule is for default constructor. So only the second rule applies to this case.
However, D::D(D&&) shouldn't have a potentially-throwing exception specification except that the expression throw Y() is considered as a subexpression of D::D(D&&).
The rule which define the immediate subexpression is as following:
The immediate subexpressions of an expression e are
the constituent expressions of e's operands
any function call that e implicitly invokes,
if e is a lambda-expression, the initialization of the entities captured by copy and the constituent expressions of the initializer of the init-captures,
if e is a function call or implicitly invokes a function, the constituent expressions of each default argument used in the call, or
if e creates an aggregate object, the constituent expressions of each default member initializer ([class.mem]) used in the initialization.
A subexpression of an expression e is an immediate subexpression of e or a subexpression of an immediate subexpression of e.
The easy way to understand the rule for subexpression is that it works recursively, that is,
immediate subexpression of immediate subexpression... of immediate subexpression of e is a subexpression of e.
I agree the expression throw Y() is a subexpression of function B(B&&, int = (throw Y(), 0)) noexcept due to the fourth bullet. However, I don't know whether B(B&&, int = (throw Y(), 0)) noexcept is considered as an implicitly invoked function which is invoked by expression D::D(D&&), which seems to obey the second bullet. if it is that, please consider the following code:
class Test{
Test(){}
~Test(){}
};
void func(){
Test t{} // implicitly invoke the defautl constructor of `Test`
// would implicitly invoke the destructor of `Test`
}
int main(){
func();
}
So, as I write in the comment, Is the constructor and the destructor of Test is considered as subexpressions of expression func()? If it's not, how to interpret the wording a subexpression of such an initialization? So, my questions are:
Q1:
In the second example, Is the implicitly invoked constructor or destructor be considered as a subexpression of expression func()?
Q2:
If the answer to the first question is no, then how to interpret a subexpression of such an initialization?
In [except.spec]/7.2, specifically the wording "a subexpression of such an initialization", what is "such an initialization"? The answer is that it must refer to an initialization described in p7.1:
a constructor selected by overload resolution in the implicit definition of the constructor for class X to initialize a potentially constructed subobject, or
It doesn't refer to the initialization of X. The text at the beginning of [except.spec]/7 only refers to a "constructor" of X, not the "initialization" of X. The natural way to read p7.2 is that "such an initialization" refers to the only preceding mention of initialization, which is in p7.1.
So the issue here, in applying [except.spec]/7.2, is not whether the constituent expressions of the default arguments of B's move constructor are subexpressions of the D initialization, but rather whether they are subexpressions of the B initialization, and the answer is that they are.
Although the above explanation should answer your main question, I will also say that I don't think the constructor of B is considered "implicitly invoked" by the constructor of D for the purposes of [intro.execution]/10. If it were, then it would mean that the initialization of the B subobject is a subexpression of the initialization of D. Instead, I believe we should consider the initialization of the B subobject as a full-expression under [intro.execution]/12.3:
A full-expression is [...] an init-declarator (Clause 11) or a mem-initializer (15.6.2), including the constituent expressions of the
initializer, [...]
Although the wording is not clear on this, I believe the initialization of the B subobject is considered to be done by a mem-initializer for the purposes of [intro.execution]. [class.copy.ctor]/14, which defines the behaviour of a defaulted move constructor, does not explicitly say that bases and members are initialized as if by a mem-initializer. However, it would be strange if a defaulted definition of a move constructor had a different subexpression hierarchy from the corresponding user-defined constructor.
If we treat the implicit call to the base class constructor as not being a mem-initializer, but rather a "function call that e implicitly invokes" under [intro.execution]/10.2, it would mean that the former is not a full-expression, and there is no point for destruction of temporaries at the end of it. It would be really strange if this were the case: it would mean temporaries created during the execution of a defaulted move constructor are destroyed at different times than temporaries created during the execution of an equivalent user-provided move constructor. A simple experiment on Godbolt reassures me that GCC and Clang do not take such a strange interpretation.
In your second example, the expression func() does not implicitly invoke anything. Functions that are invoked in the body of func() don't count. The point about implicit invocations includes such things as the destructors of temporaries at the end of a full-expression, constructors called to materialize temporary objects, constructors and conversion operators invoked by implicit conversions required by an expression, and so on.
For the first rule, the selected constructor to initialize base sub-object of type B is B (B&&, int = (throw Y(), 0)) noexcept, which is declared to have a non-throwing exception specification. This means that the constructor for B will not throw when called. However, before D calls B, it must generate the 2nd argument from its default value, which will throw. Accordingly, D will throw not because of B but because of the default parameter, which is executed outside B.
In order to see what a subexpression of an initialisation is, you need to consider the code generated by the compiler. Your call of func() does not construct an object of type T in the scope of the call, in that nothing gets injected into the scope of main by the compiler. The object is constructed in the scope of func and that does not count.
Related
struct A {
A() {}
A(const A&) = delete;
};
A foo() {
return {}; // Calls A()
}
struct B {
B(const B&) = delete;
};
B bar() {
return {}; // Aggregate initialization.
}
foo and bar both compile fine in C++11, because they use copy-list-initialization. There is no copy elision needed.
Where is it mentioned in the C++ standard that in such cases copy/move constructors are not needed?
I can see in [stmt.return] A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
I cannot find the section that mentions that in such cases copy/move constructors are not needed.
This is a C++11 (and 14, and 17) issue where aggregate-initialization is allowed to bypass the copy-constructor check.
B is an aggregate class (in C++11/14/17)
You can verify that in C++17 with the std::is_aggregate type trait
You are using list-initialization
This performs aggregate initialization, which is treated slightly differently than a regular constructor
Per #NicolBolas' nice answer regarding [stmt.return], return {} is like performing copy-list-initialization (aggregate initialzation) of the returned object directly (as opposed to constructing and then returning)
If you had written return B() instead, the compiler would have rejected this code.
The bypass is fixed in C++20 (The compiler will reject your code) per P1008 since B is no longer an aggregate type (C++20 says that a class with any user-declared constructors is not an aggregate).
Where is it mentioned in the C++ standard that in such cases copy/move constructors are not needed?
It isn't. There is no need to mention such a thing because copy-list-initialization is not specified to do any copying or moving.
The behavior of return {...}; is defined as follows:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization
So this syntax invokes copy-list-initialization, with the braced-init-list being the initializer and the function's return value object being the object to be initialized. So it is functionally equivalent to T return_value_object = {...};.
[dcl.init.list]/3 explains the entire process of list-initialization, and nowhere does it say that the object to be initialized is copied or moved from some T (where T is the object type being initialized). No temporary object of type T is created or anything of the kind. The braced-init-list simply initializes the object.
So there is no need for a copy or move constructor on T unless the members of the braced-init-list would themselves require one (if you provided an object of type T as a member of the list, for example).
Note that this is not elision. Elision implies that a copy/move would have happened but was optimized out. List initialization doesn't have any copying or moving to optimize away to begin with.
This question is about the wording of the c++ standard.
All compilers, and I think this is what should be, elide the copy constructor for the initialization of the object b bellow (assembly here):
struct B;
struct A{
operator B();
};
struct B{
B(const B&);
B(B&&);
};
void test(A a){
B b(a);
}
But when I read the standard, I do not see why this elision shall happen (bold mine) [dcl.init]/17.6.2:
Otherwise, if the initialization is direct-initialization, [...], constructors are considered.
The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
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.
It is specifically said that the constructor is called. But no compiler does it.
I imagine I am missing something or not reading correctly the standard. How should I read the standard?
This contrast with the previous and next paragraphs of the standard that specifically mandate copy elision [dcl.init]/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.
and [dlc.init]/17.6.3:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type [...]
The function selected is called with the initializer expression as its argument; [...]
The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
Where the last sentence send me back to [dcl.init]/17.6.1 which would also implies the copy elision.
#T.C. answered in a comment, this is core langugage issue CWG2327.
class AAA {
public:
explicit AAA(const AAA&) {}
AAA(int) {}
};
int main() {
AAA a = 1;
return 0;
}
In the above code, as I understand, though elided in most cases, the copy constructor is still semantically required to be called. My question is, is the call explicit or implicit? For a long time I have the conclusion in my mind that the call to AAA::AAA(int) is implicit but the call to the copy constructor is not. Today I accidentally got g++ to compile the above code and it reported error. (VC12 compiles OK.)
In section 8.5 of the standard:
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.
The bolded direct-initialize in the above quotes means the call to copy constructor is explicit, right? Is g++ wrong or my interpretation of the standard wrong?
Looks like this bug: g++ fails to call explicit constructors in the second step of copy initialization
g++ fails to compile the following code
struct X
{
X(int) {}
explicit X(X const &) {}
};
int main()
{
X x = 1; // error: no matching function for call to 'X::X(X)'
}
The second step of a copy initialization (see 8.5/16/6/2) is a
direct-initialization where explicit constructors shall be considered
as candidate functions.
Looks like copy constructor is never called . Only constructor is called . The following code may call copy constructor
AAA a = 1;
AAA ab = a;
Not sure why G++ is compiling it .
Consider the following code:
#include <iostream>
template<class T>
void f(T& t)
{
t = T();
}
int main()
{
int x = 42;
f(x);
std::cout << x;
}
Does the C++11 standard define what the output shall be? My compiler outputs 0, however I was under the impression the default constructor of a primitive type is a null operation or undefined behaviour.
There's no "default constructor" involved in your code. Only class types can have constructors. Scalar types have no constructors, default or otherwise.
The T() syntax creates a temporary object initialized by so called value-initialization. Value-initialization resolves to constructor call only for class types, and only for those with user-defined constructors (with some nuances in C++11). For other types value-initialization does not involve any constructors at all. It proceeds in accordance with its own specific and rather elaborate initialization rules that define the initial value of the data directly, without involving any constructors (see 8.5 in the language specification).
For scalar types value-initialization performs zero-initialization. This is why your code is guaranteed to output zero. The exact specifics of the abstract initialization process changed between the versions of C++ language standard, however since the beginning of times C++ language guaranteed that T() expression for T == int evaluated to zero. I.e. even in C++98 your code will output zero.
It is a common misconception that all these T(...) expressions somehow necessarily imply constructor calls. In reality, T(...) expression is a functional cast expression (regardless of the number of arguments) (see 5.2.3 in the language specification), which might resolve to constructor call in some narrow set of specific situations and has nothing to do with any constructors in other situations.
For example, this code
struct S { int x, y; };
S s = S();
is guaranteed to initialize s with zeros (both s.x and s.y) despite that fact that class S has a default constructor. I brought up this example specifically to illustrate the fact that even in situations when the default constructor exists, the T() expression can still completely ignore it and work by its own rules instead.
Here is what the standard says regarding your question:
In 8.5. paragraph 10:
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
In 8.5. paragraph 7:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the
default constructor for T is called (and the initialization is ill-formed if T has no accessible default
constructor);
if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object
is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is
called.
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
emphasis mine. So, since int isn't even a class type, it falls under the last rule, and gets zero-initialized, so it's an absolutely correct behavior.
A class must have a valid copy or move constructor for any of this syntax to be legal:
C x = factory();
C y( factory() );
C z{ factory() };
In C++03 it was fairly common to rely on copy elision to prevent the compiler from touching the copy constructor. Every class has a valid copy constructor signature regardless of whether a definition exists.
In C++11 a non-copyable type should define C( C const & ) = delete;, rendering any reference to the function invalid regardless of use (same for non-moveable). (C++11 §8.4.3/2). GCC, for one, will complain when trying to return such an object by value. Copy elision ceases to help.
Fortunately, we also have new syntax to express intent instead of relying on a loophole. The factory function can return a braced-init-list to construct the result temporary in-place:
C factory() {
return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
Edit: If there's any doubt, this return statement is parsed as follows:
6.6.3/2: "A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list."
8.5.4/1: "list-initialization in a copy-initialization context is called copy-list-initialization." ¶3: "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)."
Do not be misled by the name copy-list-initialization. 8.5:
13: The form of initialization (using parentheses or =) is generally insignificant, but does matter when the
initializer or the entity being initialized has a class type; see below. If the entity being initialized does not
have class type, the expression-list in a parenthesized initializer shall be a single expression.
14: The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
Both copy-initialization and its alternative, direct-initialization, always defer to list-initialization when the initializer is a braced-init-list. There is no semantic effect in adding the =, which is one reason list-initialization is informally called uniform initialization.
There are differences: direct-initialization may invoke an explicit constructor, unlike copy-initialization. Copy-initialization initializes a temporary and copies it to initialize the object, when converting.
The specification of copy-list-initialization for return { list } statements merely specifies the exact equivalent syntax to be temp T = { list };, where = denotes copy-initialization. It does not immediately imply that a copy constructor is invoked.
-- End edit.
The function result can then be received into an rvalue reference to prevent copying the temporary to a local:
C && x = factory(); // applies to other initialization syntax
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type? The reference trick doesn't work because a reference member doesn't extend the lifetime of a temporary.
Note, I'm not considering aggregate-initialization. This is about defining a constructor.
On your main question:
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type?
You don't.
Your problem is that you are trying to conflate two things: how the return value is generated and how the return value is used at the call site. These two things don't connect to each other. Remember: the definition of a function cannot affect how it is used (in terms of language), since that definition is not necessarily available to the compiler. Therefore, C++ does not allow the way the return value was generated to affect anything (outside of elision, which is an optimization, not a language requirement).
To put it another way, this:
C c = {...};
Is different from this:
C c = [&]() -> C {return {...};}()
You have a function which returns a type by value. It is returning a prvalue expression of type C. If you want to store this value, thus giving it a name, you have exactly two options:
Store it as a const& or &&. This will extend the lifetime of the temporary to the lifetime of the control block. You can't do that with member variables; it can only be done with automatic variables in functions.
Copy/move it into a value. You can do this with a member variable, but it obviously requires the type to be copyable or moveable.
These are the only options C++ makes available to you if you want to store a prvalue expression. So you can either make the type moveable or return a freshly allocated pointer to memory and store that instead of a value.
This limitation is a big part of the reason why moving was created in the first place: to be able to pass things by value and avoid expensive copies. The language couldn't be changed to force elision of return values. So instead, they reduced the cost in many cases.
Issues like this were among the prime motivations for the change in C++17 to allow these initializations (and exclude the copies from the language, not merely as an optimization).