Does an unmaterialized temporary needs the destructor to be accessible? - c++

GCC 7.2 and Clang 5.0 do not agree in this case:
struct A;
A foo();
struct A
{
static void bar()
{
foo();
}
private:
~A()=default;
};
A foo()
{
return {};
//GCC error: A::~A() is private in this context.
};
This behavior is part of the "c++17 guaranteed (copy elision and is not related RVO or NRVO)."
GCC does not compile this code but Clang does. Which one is wrong?
Maybe this paragraph says that the bot Clang and GCC are standard compliant [class.temporary]:
When an object of class type X is passed to or returned from a function, if each copy constructor, move
constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move
constructor, implementations are permitted to create a temporary object to hold the function parameter or
result object. The temporary object is constructed from the function argument or return value, respectively,
and the function’s parameter or return object is initialized as if by using the non-deleted trivial constructor
to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution
to perform a copy or move of the object). [ Note: This latitude is granted to allow objects of class type to be
passed to or returned from functions in registers. — end note ]

I believe this is a clang bug.
From [class.temporary]:
Temporary objects are created [...] when needed by the implementation to pass or return an object of trivially-copyable type (see below), and [...]
Even when the creation of the temporary object is unevaluated ([expr.prop]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed. [ Note: This includes accessibility and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of the operand of a decltype-specifier ([expr.call]), no temporary is introduced, so the foregoing does not apply to such a prvalue. — end note ]
The copy-initialization of the return of foo is a context that creates a temporary object, so the semantic restrictions must still be followed - which include the accessibility of the destructor (as the note helps make clear). ~A() must be accessible in this context, and isn't, so the program should be ill-formed. gcc is correct to reject.

I believe this is a gcc bug in C++17.
According to copy elision:
since C++17
Under the following circumstances, the compilers are required
to omit the copy- and move- construction of class objects even
if the copy/move constructor and the destructor have
observable side-effects. They need not be present or accessible,
as the language rules ensure that no copy/move operation takes
place, even conceptually:
In initialization, 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.
In a function call, if the operand of a return statement is a
prvalue and the return type of the function is the same as the
type of that prvalue.
Your function foo returns a prvalue object of type A and the return
type of foo is A, no matter whether A has accessible copy/move
constructor and destructor, the copy will be omitted in C++17.

Related

Unexpected copy constructor

In the following example I am expecting only a single copy-construction, as I thought the intermediate copies would by copy elided. The only required (I thought?) copy would be in the constructor of B to initialize the member variable a.
#include <iostream>
struct A
{
A() = default;
A(A const&) { std::cout << "copying \n"; }
};
struct B
{
B(A _a) : a(_a) {}
A a;
};
struct C : B
{
C(A _a) : B(_a) {}
};
int main()
{
A a{};
C c(a);
}
When I execute this code (with -O3) I see the following output
copying
copying
copying
Why aren't these intermediate copies elided?
Here are the cases, where copy elision is allowed (class.copy/31):
in a return statement in a function with a class return type, when the expression is the name of a
non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-
unqualified type as the function return type, the copy/move operation can be omitted by constructing
the automatic object directly into the function’s return value
in a throw-expression, when the operand is the name of a non-volatile automatic object (other than
a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost
enclosing try-block (if there is one), the copy/move operation from the operand to the exception
object (15.1) can be omitted by constructing the automatic object directly into the exception object
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the omitted copy/move
when the exception-declaration of an exception handler (Clause 15) declares an object of the same type
(except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted
None of these are true for your example (we are not in a return statement, throw-expression or exception-declaration. And there are no temporaries in your example at all.), so copy happens each time you expect to happen.
Note, that copy elision is allowed in the mentioned cases, but not mandatory. So even, for these cases, the compiler is allowed to emit copies (this is true for C++11. In C++17, there are some cases, where copy elision is mandatory. But, none of your example cases allows elision in C++17 either.)
Expanding on the discussion under the other answer, the copy here has side effects (printing to console), and so does not qualify for copy optimization.
Copy elision, however, is allowed regardless of side effects, per the cppreference article on copy elision. It's not allowed here because you have lvalues all the way. None of the copies you hoped to eliminate were of an rvalue or prvalue. To make them an rvalue, you need to cast using std::move or construct them as nameless temporaries as part of the constructor call.
Again, the cppreference article explains it much better.
You are passing the a object to the C constructor by value. And then it is being passed to the B constructor by value.

What changes to C++ made copy initialization work for class with explicit constructor?

Consider this code:
struct X{
explicit X(){}
explicit X(const X&){}
};
void foo(X a = X()){}
int main(){}
Using C++14 standard, both GCC 7.1 and clang 4.0 rejects the code, which is what I expected.
However, using C++17 (-std=c++1z), they both accept the code. What rule changed?
For both compilers to exhibit this same behavior, I doubt this to be a bug. But as far as I can tell, the latest draft still says, default argument uses the semantics of copy-initialization 1. Again, we know that explicit constructors will only allow direct initialization 2.
1: dcl.fct.default/5;
2: class.conv.ctor/2
Because the behavior of copy elision changes from C++17; for this case copy elision is mandatory.
Mandatory elision of copy/move operations
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
In the initialization of an object, when the initializer expression is
a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T f() {
return T();
}
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
And for copy initialization:
The effects of copy initialization are:
First, if T is a class type and the initializer is a prvalue
expression whose cv-unqualified type is the same class as T, the
initializer expression itself, rather that a temporary materialized
from it, is used to initialize the destination object: see copy
elision (since C++17)
If T is a class type and the cv-unqualified version of the type of
other is T or a class derived from T, the non-explicit constructors of
T are examined and the best match is selected by overload resolution.
The constructor is then called to initialize the object.
That means for X a = X(), a will be default constructed directly, the copy/move constructors and their side effects will be omiited completely. The selection of non-explicit constructors for overload resolution won't take place, which is required in C++14 (and before). For these guaranteed cases, the copy/move constructors don't participate in, then it won't matter whether they're explicit or not.
The most important for the example in the question rule is [expr.type.conv]/2. But lets start from [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.
...
(17.6) — If the destination type is a (possibly cv-qualified) class type:
— 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]
So, in X a = X(), the initializer expression X() is used to initialize the destination object. Of course, this is not enough to answer: why default constructor is selected (i.e. how X() becomes ()) and why explicit default constructor is fine.
The X() expression is explicit type conversion in functional notation, so lets look into [expr.type.conv]/2:
If the initializer is a parenthesized single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression. If the type is cv void and the initializer is (), 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.
Emphasis of the relevant sentence is mine. It says that for X():
the object is initialized with () (it is "the initializer" by [expr.type.conv]/1), that's why the default constructor is selected;
the object is direct-initialized, that's why it is OK that the default constructor is explicit.
In more details: when the initializer is (), [dcl.init]/(17.4) apply:
If the initializer is (), the object is value-initialized.
[dcl.init]/8:
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
[dcl.init]/7:
To default-initialize an object of type T means:
— If T is a (possibly cv-qualified) class type, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution. The constructor thus selected is called, with an empty argument list, to initialize the object.
[over.match.ctor]/1
When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized.
In C++14, [dcl.init](17.6) was saying:
— 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 ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
So for X a = X(), only converting (non-explicit) constructors accepting one argument of type X will be considered (which are copy and move constructors).

Why the expression 'T foo = T()' is not copy initialized [duplicate]

Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
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.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. In
such cases, the implementation treats the source and target of the
omitted copy/move operation as simply two different ways of referring
to the same object, and the destruction of that object occurs at the
later of the times when the two objects would have been destroyed
without the optimization. This elision of copy/move operations,
called copy elision, is permitted in the following circumstances
(which may be combined to eliminate multiple copies):
[...]
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be
omitted by constructing the temporary object directly into the target of the omitted copy/move
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]

Why only call constructor one time? [duplicate]

Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
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.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. In
such cases, the implementation treats the source and target of the
omitted copy/move operation as simply two different ways of referring
to the same object, and the destruction of that object occurs at the
later of the times when the two objects would have been destroyed
without the optimization. This elision of copy/move operations,
called copy elision, is permitted in the following circumstances
(which may be combined to eliminate multiple copies):
[...]
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be
omitted by constructing the temporary object directly into the target of the omitted copy/move
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]

Why copy constructor is not called in this case?

Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
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.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. In
such cases, the implementation treats the source and target of the
omitted copy/move operation as simply two different ways of referring
to the same object, and the destruction of that object occurs at the
later of the times when the two objects would have been destroyed
without the optimization. This elision of copy/move operations,
called copy elision, is permitted in the following circumstances
(which may be combined to eliminate multiple copies):
[...]
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be
omitted by constructing the temporary object directly into the target of the omitted copy/move
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]