Copy semantics for C++ unique pointer - c++

Is there something wrong if I write something like this:
Try<std::unique_ptr<int> > some_function() {
std::unique_ptr<int> s(new int(2));
return s;
}
Is the copy constructor invoked? Should I use std::move?

std::unique_ptr doesn't have a copy constructor. What you're doing there is the same as assigning with a unique_ptr: the pointer is moved. (though in some situations you have to explicitly move() the pointer or else you'll get a compilation error; but if the compiler doesn't complain with an error, then it's quietly moving the pointer)

In a return statement, overload resolution can be performed as if the id-expression in the return statement designates an rvalue:
When the criteria for elision of a copy/move operation are met, [..], 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 [..], overload resolution
to select the constructor for the copy is first performed as if the object were designated by an rvalue.
So your case does indeed qualify for NRVO since s is declared in the body of the function, thus std::move() is not required as overload-resolution can treat s as an rvalue.
Note that std::move() might still be needed if your compiler doesn't support the first phase of overload resolution treating s as an rvalue when the type of the return expression doesn't have the same cv-unqualified type as the function's return type. This seems to be the case with the trunk version of clang but not gcc. More info in this thread.

Related

The local variable which is used in return statement doesn't convert to r-value implicitly to match the conversion operator

In the example snippet code below, the local variable which is used in return statement doesn't convert to r-value implicitly to match the conversion operator. However for move constructor it works.
I want to know whether it is a standard behavior or a bug. And if it is a standard behavior, what is the reason?
I tested it in Microsoft Visual Studio 2019 (Version 16.8.3) in 'permissive-' mode and it produced a compiler error. But in 'permissive' mode, it was OK.
#include <string>
class X
{
std::string m_str;
public:
X() = default;
X(X&& that)
{
m_str = std::move(that.m_str);
}
operator std::string() &&
{
return std::move(m_str);
}
};
X f()
{
X x;
return x;
}
std::string g()
{
X x;
return x; // Conformance mode: Yes (/permissive-) ==> error C2440: 'return': cannot convert from 'X' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
//return std::move(x); // OK
// return X{}; // OK
}
int main()
{
f();
g();
return 0;
}
The reason that f works under the C++11 standard (link is to a close-enough draft) is this clause
[class.copy]/32
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. ...
And the "criteri[on] for elision of a copy operation" that is relevant in this case is
[class.copy]/31.1
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
This works for f, since x in return x is the "name of a non-volatile automatic object ... with the same cv-unqualified type as the function return type"; that type is X. This does not work for g, since the return type std::string is not the type X of the object named by x.
I think it might be important to understand why this rule is here in the first place. This rule isn't really about implicitly moving function-local variables into function return values, even though that's what it literally says. It's about making NRVO possible. Consider what you would have to write for f without these rules:
X f() {
X x;
return std::move(x);
}
But then NVRO cannot apply, since you aren't returning a variable; you're returning the result of a function call! So the clause [class.copy]/32 is about making your code
X f() {
X x;
return x;
}
syntactically legal, while the semantics as described by the clause (using a move constructor) are to be ignored (assuming your implementation isn't too stupid) because we're actually just going to do NRVO, which doesn't call anything.
You see that, really, [class.copy]/32 doesn't have to work for g. Its purpose in f is to make it possible to execute zero copy/move constructors. But g has to execute the conversion operator; there's no other sensible way for the language to pull out a std::string when you give it an X. So NVRO cannot apply in g, so there's no need to write return x;, so you can just write
std::string g() {
X x;
return std::move(x);
}
and not be worried that will cause a missed optimization.
We see that the C++11 rule [class.copy]/32 is designed so that it affects the minimal portion of the cases possible. It applies to those cases where'd we'd like NVRO but don't have a copy constructor, and makes NVRO possible by telling us to pretend we'll call the move constructor. But when actually writing code, that means it's a mind-twister of a rule to remember: "to minimize copies/moves, if the return type is the same as the type of the variable, return the_variable;, and otherwise return std::move(the_variable)." That's why the C++20 standard completely rephrases [class.copy]/32 into
[class.copy.elision]/3
An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:
If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
...
overload resolution to select the constructor for the copy or the return_­value overload to call is first performed as if the expression or operand were an rvalue. ...
This does not require that the return type be the same as the variable's type for an implicit move; it can be summed up as the conceptually simpler rule "returning a variable tries to move, and then tries to copy". That leads to the conceptually simpler principle "When returning a variable from a function, just return the_variable; and it will do the right thing". (Of course, none of GCC, Clang, or MSVC seem to have gotten the memo. That has to be some kind of record...)

How to do type conversion on an "implicit" rvalue in the return statement

Is this invalid? gcc accepts it, clang and msvc don't.
#include <memory>
class Holder {
std::unique_ptr<int> data;
public:
operator std::unique_ptr<int>() && { return std::move(data); }
};
std::unique_ptr<int> test()
{
Holder val;
return val;
}
Assuming that I don't want to add something like std::unique_ptr<int> Holder::TakeData() { return std::move(data); }, the only other workaround I could think of is moving in the return value:
std::unique_ptr<int> test()
{
Holder val;
return std::move(val); // lets the conversion proceed
}
But then gcc 9.3+ has the gall to tell me that the std::move is redundant (with all warnings enabled). WTF? I mean yeah, gcc doesn't need the move, sure, but nothing else accepts the code then. And if it won't be gcc, then some humans inevitably will balk at it later.
What's the authoritative last word on whether it should compile as-is or not?
How should such code be written? Should I put in this seemingly noisy TakeData function and use it? Worse yet - should I maybe make the TakeData function limited to rvalue context, i.e. having to do return std::move(val).TakeData() ?
Adding operator std::unique_ptr<int>() & { return std::move(data); } is not an option, since it obviously leads to nasty bugs - it can be invoked in wrong context.
The "implicit" rvalue conversion is standard mandated. But depending on which standard version you are using, which compiler is "correct" varies.
In C++17
[class.copy.elision] (emphasis mine)
3 In the following copy-initialization contexts, a move operation
might be used instead of a copy operation:
If 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, or
...
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, or if the type of the
first parameter of the selected constructor is not an rvalue reference
to the object's type (possibly cv-qualified), overload resolution is
performed again, considering the object as an lvalue. [ Note: This
two-stage overload resolution must be performed regardless of whether
copy elision will occur. It determines the constructor to be called if
elision is not performed, and the selected constructor must be
accessible even if the call is elided.  — end note ]
Up to C++17, GCC is wrong. Using val implicitly as an rvalue should fail to initialize the return type on account of the sentence I marked in bold (the rvalue reference in the unique_ptr c'tor doesn't bind directly to val). But come C++20, that sentence is no longer there.
C++20
3 An implicitly movable entity is a variable of automatic storage
duration that is either a non-volatile object or an rvalue reference
to a non-volatile object type. In the following copy-initialization
contexts, a move operation might be used instead of a copy operation:
If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized)
id-expression that names an implicitly movable entity declared in the
body or parameter-declaration-clause of the innermost enclosing
function or lambda-expression, or
[...]
overload resolution to select the constructor for the copy or the
return_­value overload to call is first performed as if the expression
or operand were an rvalue. If the first overload resolution fails or
was not performed, overload resolution is performed again, considering
the expression or operand as an lvalue. [ Note: This two-stage
overload resolution must be performed regardless of whether copy
elision will occur. It determines the constructor or the return_­value
overload to be called if elision is not performed, and the selected
constructor or return_­value overload must be accessible even if the
call is elided. — end note ]
The correctness of the code is thus subject to the time travel properties of your compiler(s).
As far as how should code like that should be written. If you aren't getting consistent results, an option would be to use the exact return type of the function
std::unique_ptr<int> test()
{
Holder val;
std::unique_ptr<int> ret_val = std::move(val);
return ret_val;
}
I agree from the get go that this may not look as appealing as simply returning val, but at least it plays nice with NRVO. So we aren't likely to get more copies of unique_ptr than we desired originally.
If that is too unappealing still, then I find your idea of a resource stealing member function to be most to my liking. But no accounting for taste.

Does NRVO also apply to coroutines?

In the following example, NRVO (Named Return Value Optimization) applies as per this article:
std::string f1()
{
std::string str;
return str; // NVRO applies here!
}
However, consider:
task<std::string> f2()
{
std::string str;
co_return str; // Does NVRO also apply here?
}
I know that NRVO (Named Return Value Optimization) is mandatory since C++17:
It is not. NRVO is still an optimisation.
The non-named Return Value Optimisation (RVO) is mandatory.
// Is NVRO also guaranteed here?
No, because NRVO is never guaranteed.
For completeness, guaranteed elision in C++17 only applies to returning a prvalue from a function directly. Returning a named variable is only subject to elision if the compiler feels like it.
As for the meat of your question, co_return values are never subject to copy elision, guaranteed or otherwise. Elision for return values keys off of the return keyword, and coroutines aren't allowed to use return. They use co_return, which the elision logic in the standard doesn't key off of. So elision does not apply.
The reason why this was done is because of how coroutines work. A coroutine is a function that has a promise object in it. This promise object is how you shepard the coroutine's co_return value (and other state) to the "future" object that the coroutine function returned.
Elision works in normal functions because calling conventions require the caller to pass the storage for a return value to the function. So the function's implementation can choose to just build the object in that storage rather than building a separate stack object and copying into it upon return.
In a coroutine, the return value lives inside the promise, so that can't really happen.
NRVO as defined by the article you linked (i.e. not even creating a temporary) isn't a thing for coroutines because how co_return works is up to the user-provided coroutine promise type: the expression in the co_return statement is fed to the promise's return_value method, which can decide what to do with it.
However there is a related optimization that still may be useful. [class.copy.elision]/3 says the following:
An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:
If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
[...]
overload resolution to select the constructor for the copy or the return_­value overload to call is first performed as if the expression or operand were an rvalue. If the first overload resolution fails or was not performed, overload resolution is performed again, considering the expression or operand as an lvalue.
This means that if you return a local variable by name from a coroutine it will be moved, not copied (as long as the promise type supports this). For example, clang accepts the following despite the fact that it's not possible to copy a std::unique_ptr<int>:
// Assume a coroutine task type called Task<T> whose associated promise has a
// return_value(T) method. The co_return here will successfully call that
// method.
Task<std::unique_ptr<int>> MakeInt() {
auto result = std::make_unique<int>(17);
co_return result;
}
So the optimization "value is fed to coroutine promise as an rvalue reference even though std::move wasn't used" does apply. But the standard doesn't say "move constructor isn't even called", and it can't because it's up to the promise what to do with the expression it's given.
Just because answer looks too long. The standard says that the statement co_return <expr>; is equivalent to:
P.return_value(<expr>);
where P is the coroutine's promise object. From that I suppose you can answer this question and many other questions.
If you are looking for coroutine documentation look here:
dcl.fct.def.coroutine
stmt.return.coroutine
expr.await
expr.yield
support.coroutine

Returning move-only type compiles even though copy-constructor is unavailable

The following compiles without error:
#include <memory>
std::unique_ptr<int> f() {
std::unique_ptr<int> x(new int(42));
return x;
}
int main() {
std::unique_ptr<int> y = f();
}
I thought that the return value of f() was copy-initialized by x, but std::unique_ptr is a move-only type. How is it that this isn't ill-formed because the copy constructor isn't available? What is the relevant clause in the standard? Is there somewhere that says if f() is a move-only type than a return statement becomes a move construction instead of a copy construction?
I thought that the return value of f() was copy-initialized by x, but std::unique_ptr is a move-only type
The return value of f() is indeed copy-initialized from the expression x, but copy-initialization does not always imply copy-construction. If the expression is an rvalue, then the move constructor will be picked by overload resolution (assuming a move constructor is present).
Now although it is true that the expression x in the return x; statement is an lvalue (which may lead you to think that what I just wrote does not apply), in situations where a named object with automatic storage duration is returned, the compiler shall first try to treat the id-expression as an rvalue for overload resolution.
What is the relevant clause in the standard? Is there somewhere that says if f() is a move-only type than a return statement becomes a move construction instead of a copy construction?
Per paragraph 12.8/32 of the C++ Standard ([class.copy]/32, draft N4296):
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. [...]

Result of ternary operator not an rvalue

If you compile this program with a C++11 compiler, the vector is not moved out of the function.
#include <vector>
using namespace std;
vector<int> create(bool cond) {
vector<int> a(1);
vector<int> b(2);
return cond ? a : b;
}
int main() {
vector<int> v = create(true);
return 0;
}
If you return the instance like this, it is moved.
if(cond) return a;
else return b;
Here is a demo on ideone.
I tried it with gcc 4.7.0 and MSVC10. Both behave the same way.
My guess why this happens is this:
The ternary operators type is an lvalue because it is evaluated before return statement is executed. At this point a and b are not yet xvalues (soon to expire).
Is this explanation correct?
Is this a defect in the standard?
This is clearly not the intended behaviour and a very common case in my opinion.
Here are the relevant Standard quotes:
12.8 paragraph 32:
Copy elision is permitted in the following circumstances [...]
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
[when throwing, with conditions]
[when the source is a temporary, with conditions]
[when catching by value, with conditions]
paragraph 33:
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, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. - end note]
Since the expression in return (cond ? a : b); is not a simple variable name, it's not eligible for copy elision or rvalue treatment. Maybe a bit unfortunate, but it's easy to imagine stretching the example a little bit further at a time until you create a headache of an expectation for compiler implementations.
You can of course get around all this by explicitly saying to std::move the return value when you know it's safe.
This will fix it
return cond ? std::move(a) : std::move(b);
Consider the ternary operator as a function, like your code is
return ternary(cond, a, b);
The parameters won't be moved implicitly, you need to make it explicit.