polymorphic unique_ptr copy elision - c++

I have the following code that works on Clang 5.0 but not on Clang 3.8, with C++14 enabled:
class Base {};
class Derived : public Base {};
std::unique_ptr<Base> MakeDerived()
{
auto derived = std::make_unique<Derived>();
return derived;
}
int main()
{
auto base = MakeDerived();
std::cout << "Type: " << typeid(base).name() << '\n';
}
Live Sample Here
Is the return value technically being move-constructed in this case, due to copy elision? And if so, does that mean unique_ptr's move constructor is designed to support implicit upcasts of user class types? It's hard to tell from the cppreference documentation (#6), unless I'm supposed to assume on that documentation page that template type U is distinctly different from type T for the class itself.
The one thing that makes it obvious this works is that unique_ptr is not copy constructible, and since I don't convert it to an rvalue via std::move(), there's no other explanation as to why the code would compile other than copy elision. However because it doesn't work with clang 3.8, which accepts the -std=c++14 flag, I want to make sure the standard itself guarantees that somewhere (and if so, where) and this is just a compiler bug or lack of support issue in v3.8 of Clang.

Copy elision (before C++17) always requires the elided constructor to be actually accessible, so it cannot be the cause of your code working.
Note, however, that C++ (since C++11) has a rule (12.8/32) which boils down to the following: "when returning an object, then under certain circumstances, first try treating the object as an rvalue and only if that fails, treat it as the lvalue it is."
In C++11, these "certain circumstances" were "copy elision is possible," which requires the returned object and the return type to be the same type (modulo cv qualification).
In C++14, these "certain circumstances" were relaxed to "copy elision is possible, or the returned object is a local variable in the function."
So in C++11, the code fails, because std::unique_ptr<Derived> (the type of derived) is different from std::unique_ptr<Base> (the return type), and so std::move would have to be used.
In C++14, the code succeeds, because derived is a local in the function, and is thus treated as an rvalue first. This makes the constructor template you were referring to applicable:
std::unique_ptr<T> can be constructed from an rvalue of type std::unique_ptr<U> if U* can be converted to T*.
Your Clang 3.8 seems to be exhibiting the C++11 behaviour; perhaps it has not (yet/fully) implemented the relaxed aspects of C++14 in that version.
(And yes, indeed you're supposed to assume that the "template type" U is distinct from the class's template parameter T. That's why it was introduced in the first place: the constructor being described by #6 is a constructor template).

That return invokes unique_ptr's converting move constructor template, template<class U, class E> unique_ptr(unique_ptr<U, E>&&). There's no elision; it depends on an implicit move from derived enabled by the resolution of core issue 1579.

Related

Construction of lambda object in case of specified captures in C++

Starting from C++20 closure types without captures have default constructor, see https://en.cppreference.com/w/cpp/language/lambda:
If no captures are specified, the closure type has a defaulted default constructor.
But what about closure types that capture, how can their objects be constructed?
One way is by using std::bit_cast (provided that the closure type can be trivially copyable). And Visual Studio compiler provides a constructor for closure type as the example shows:
#include <bit>
int main() {
int x = 0;
using A = decltype([x](){ return x; });
// ok everywhere
constexpr A a = std::bit_cast<A>(1);
static_assert( a() == 1 );
// ok in MSVC
constexpr A b(1);
static_assert( b() == 1 );
}
Demo: https://gcc.godbolt.org/z/dnPjWdYx1
Considering that both Clang and GCC reject A b(1), the standard does not require the presence of this constructor. But can a compiler provide such constructor as an extension?
But what about closure types that capture, how can their objects be constructed?
You can't. They can only be created from the lambda expression.
And no, bit_cast does not "work everywhere". There is no rule in the C++ standard which requires that any particular lambda type must be trivially copyable (or the same size as its capture member for that matter). The fact that no current implementations break your code does not mean that future implementations cannot.
And it definitely won't work if you have more than one capture member.
Just stop treating lambdas like a cheap way to create a type. If you want to make a callable type with members that you can construct, do that:
#include <bit>
int main() {
struct A
{
int x = 0;
constexpr auto operator() {return x;}
};
// ok everywhere
constexpr A b(1);
static_assert( b() == 1 );
}
Since this is tagged language-lawyer, here's what the C++ standard has to say about all this.
But what about closure types that capture, how can their objects be constructed?
The actual part of the standard that cppreference link is referencing is [expr.prim.lambda.general] - 7.5.5.1.14:
The closure type associated with a lambda-expression has no default constructor if the lambda-expression has a lambda-capture and a defaulted default constructor otherwise. It has a defaulted copy constructor and a defaulted move constructor ([class.copy.ctor]). It has a deleted copy assignment operator if the lambda-expression has a lambda-capture and defaulted copy and move assignment operators otherwise ([class.copy.assign]).
However, clauses 1 and 2 say:
The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.
The closure type is not an aggregate type. An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:
[unrelated stuff]
Which means that (apart from the unrelated exceptions), the described interface of the lambda as stated is exhaustive. Since no other constructors than the default one is listed, then that's the only one that is supposed to be there.
N.B. : A lambda may be equivalent to a class-based functor, but it is not purely syntactical sugar. The compiler/implementation does not need a constructor in order to construct and parametrize the lambda's type. It's just programmers who are prevented from creating instances by the lack of constructors.
As far as extensions go:
But can a compiler provide such constructor as an extension?
Yes. A compiler is allowed to provide this feature as an extension as long as all it does is make programs that would be ill-formed functional.
From [intro.compliance.general] - 4.1.1.8:
A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this document. Having done so, however, they can compile and execute such programs.
However, for the feature at hand, MSVC would be having issues in its implementation as an extension:
It should be emmiting a diagnostic.
By its own documentation, it should refuse the code when using /permissive-. Yet it does not.
So it looks like MSVC is, either intentionally or not, behaving as if this was part of the language, which is not the case as far as I can tell.

copy elision in c++03

Copy-elision is, in some cases, mandatory in c++17, and permitted in c++11/14. This in particular concerns copy initialization.
For example, the following program
#include <iostream>
struct A
{
explicit A(int){ std::cout << "conversion" << std::endl; }
A(const A&) { std::cout << "copy constructor" << std::endl; }
};
int main()
{
A b = A(3);
}
is expected in c++17 to produce an output
conversion
and in c++11/14 may produce the same output. With these regards, both gcc 10.1.0 and clang 11.1.0 produce the output above also with -std=c++11 or -std=c++14, unless one explicitly disables the optional constructors elision with -fno-elide-constructors.
But what about c++03 standard? Was it allowed to elide the copy constructors in the copy initialization? gcc and clang with -std=c++03 always suppress the copy constructor (unless one specifies -fno-elide-constructors).
Yes, copy ellision is permitted in C++03 and C++98. That's the paragraph for C++98 and C++03:
Non-mandatory elision of copy operations
Under the following circumstances, the compilers are permitted, but
not required to omit the copy construction of
class objects even if the copy constructor and the
destructor have observable side-effects. The objects are constructed
directly into the storage where they would otherwise be copied
to. This is an optimization: even when it takes place and the
copy constructor is not called, it still must be
present and accessible (as if no optimization happened at all),
otherwise the program is ill-formed:
In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a
function parameter or a catch clause parameter, and which is of the
same class type (ignoring cv-qualification) as the function return
type. This variant of copy elision is known as NRVO, "named return
value optimization".
In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring
cv-qualification) as the target object. When the nameless temporary is
the operand of a return statement, this variant of copy elision is
known as RVO, "return value optimization".
When copy elision occurs, the implementation treats the source and target of the omitted copy 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
cppreference
I removed everything that's only valid since C++11.
The only differences between C++98, C++03 and C++11 regarding ellision are move operations and exception handling.

Assuming no compiler optimization, how many times will this object be created?

Assuming there is no compiler optimization. How many times would OutputBuffer_s type object will be created?
#include <iostream>
#include <vector>
struct OutputBuffer_s {
int encoded[10];
};
OutputBuffer_s func() {
OutputBuffer_s s;
return s;
}
int main() {
OutputBuffer_s a = func();
}
Initially, I had assumed three times.
1) When func() is called, object s will be created on stack.
2) When func() goes out of scope, it will return copy of object s to main().
3) Copying of value to object a in main(), since value returned by func() would be a temporary.
I know that I'm wrong here, since I compiled with -O0 in g++ but I could see only one creation after overriding the constructors. I want to know where and why I am wrong.
What you have here copy-elison.
Omits copy and move (since C++11) constructors, resulting in zero-copy pass-by-value semantics.
GCC can elide the constructors even with -O0 option. This is what is happening here. If you want to specifically prevent elision, you can use the -fno-elide-constructors option.
If you use this option, there will be one constructor call and two move constructor calls for C++11.
See demo here.
If you use C++17, there is guaranteed copy-elision in some cases, and here even with the -fno-elide-constructors option, there will be one constructor call and just one move constructor call.
See demo here.
C++17 has introduced Temporary materialization which I quote:
A prvalue of any complete type T can be converted to an xvalue of the same type T. This conversion initializes a temporary object of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object. If T is a class or array of class type, it must have an accessible and non-deleted destructor.
In that case the extra calls to the contructor will become a move operation. Prior to C++17, which copy elision was not mandatory, the compiler would usually copy elide. As far as I am aware, in your case, a compiler would copy elide anyway (try godbolt and check the produced assembly).
To fully answer, one call to the constructor and one move.

Returning local unique_ptr as a shared_ptr

I'm used to not use std::move when returning a std::unique_ptr, because doing so prohibits RVO. I have this case where I have a local std::unique_ptr, but the return type is a std::shared_ptr.
Here's a sample of the code:
shared_ptr<int> getInt1() {
auto i = make_unique<int>();
*i = 1;
return i;
}
shared_ptr<int> getInt2() {
return make_unique<int>(2);
}
unique_ptr<int> getInt3() {
auto ptr = make_unique<int>(2);
return ptr;
}
int main() {
cout << *getInt1() << endl << *getInt2() << *getInt3() << endl;
return 0;
}
GCC accepts both cases, but Clang refuses the getInt1() With this error:
main.cpp:10:13: error: no viable conversion from 'std::unique_ptr<int, std::default_delete<int> >' to 'shared_ptr<int>'
return i;
^
Here's both cases on coliru: GCC, Clang
Both compiler accept the third case.
Which one is wrong? Thanks.
The correct answer depends on which C++ standard you are talking about.
If we are talking about C++11, clang is correct (an explicit move is needed). If we are talking about C++14, gcc is correct (an explicit move is not needed).
C++11 says in N3290/[class.copy]/p32:
When the criteria for elision of a copy operation are met or would be
met save for the fact that the source object is a function parameter,
and the object to be copied is designated by an lvalue, overload
resolution to select the constructor for the copy is first performed
as if the object were designated by an rvalue. If overload resolution
fails, ...
This demands that you only get the implicit move when the return expression has the same type as the function return type.
But CWG 1579 changed this, and this defect report was accepted after C++11, and in time for C++14. This same paragraph now reads:
When the criteria for elision of a copy/move operation are met, but
not for an exception-declaration, and the object to be copied is
designated by an lvalue, or when the expression in a return statement
is a (possibly parenthesized) id-expression that names an object with
automatic storage duration declared in the body or
parameter-declaration-clause of the innermost enclosing function or
lambda-expression, overload resolution to select the constructor for
the copy is first performed as if the object were designated by an
rvalue. If the first overload resolution fails or was not performed, ...
This modification basically allows the return expression type to be convertible-to the function return type and still be eligible for implicit move.
Does this mean that the code needs a #if/#else based on the value of __cplusplus?
One could do that, but I wouldn't bother. If I were targeting C++14, I would just:
return i;
If the code is unexpectedly run under a C++11 compiler, you will be notified at compile-time of the error, and it is trivial to fix:
return std::move(i);
If you are just targeting C++11, use the move.
If you want to target both C++11 and C++14 (and beyond), use the move. The downside of using move gratuitously is that you can inhibit RVO (Return Value Optimization). However, in this case, RVO is not even legal (because of the conversion from the return statement to the return type of the function). And so the gratuitous move does not hurt anything.
The one time you might lean towards a gratuitous move even when targeting C++14 is if without it, things still compile in C++11, and invoke an expensive copy conversion, as opposed to a move conversion. In this case, accidentally compiling under C++11 would introduce a silent performance bug. And when compiled under C++14 the gratuitous move still has no detrimental effects.
std::unique_ptr could be used to construct std::shared_ptr only when it's a rvalue. See the constructor declaration of std::shared_ptr:
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r );
So you need to use std::move to make the 1st case work, otherwise it should fail.
return std::move(i);
BTW: I compiled the code with gcc 4.9.3 it failed either.
source_file.cpp:14:12: error: cannot bind ‘std::unique_ptr<int, std::default_delete<int> >’
lvalue to ‘std::unique_ptr<int, std::default_delete<int> >&&’
return i;
^

When is explicit move needed for a return statement?

In a comment to another question Jonathan Wakely responds to my statement:
You never need explicit move for a local variable function return
value. It's implicit move there
->
... never say never ... You need an explicit move if the local variable
is not the same type as the return type, e.g. std::unique_ptr<base>
f() { auto p = std::make_unique<derived>(); p->foo(); return p; }, but
if the types are the same it will move if possible ...
So it seems sometimes we may have to move a local variable on return.
The example
std::unique_ptr<base> f() {
auto p = std::make_unique<derived>();
p->foo();
return p;
}
is nice in that it gives a compilation error
> prog.cpp:10:14: error: cannot convert ‘p’ from type
> ‘std::unique_ptr<derived>’ to type ‘std::unique_ptr<derived>&&’
but I'm wondering whether there is a good chance to detect this in general -- and is this here a limit of the language rules or of unique_ptr??
Update:
Explicit move should not be needed in modern compiler versions.
Core DR 1579 changed the rules so that the return value will be treated as an rvalue even when the types are not the same. GCC 5 implements the new rule, for C++11 as well as C++14.
Original answer:
This is not a limitation of unique_ptr, it's a limitation of the language, the same limitation applies to any return statement that calls a converting constructor taking an rvalue reference:
struct U { };
struct T {
T(U&&) { }
};
T f() {
U u;
return u; // error, cannot bind lvalue to U&&
}
This will not compile because [class.copy]/32 says:
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
This means that the expression in a return statement can only be treated as an rvalue if it is eligible for copy/move elision (aka NRVO) but that is too restrictive because it means it only applies when the type is exactly the same, even though the variable is always going out of scope so it would be reasonable to always treat is as an rvalue (technically as an xvalue, an expiring value.)
This was recently suggested by Richard Smith (and previously by Xeo) and I think it's a very good idea.