Result of ternary operator not an rvalue - c++

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.

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.

Move-only type returned into converting constructor

The following code returns a move-only type that should then be converted to another type by a converting constructor.
#include <utility>
class Foo
{
public:
Foo() {}
Foo(const Foo&) = delete;
Foo(Foo&&) = default;
};
class Other
{
public:
Other(Foo foo) {}
};
Other moo()
{
Foo foo;
return foo;
}
int main()
{
moo();
}
This threw me an error with my compiler and could only be fixed by adding std::move to the return statement which is considered bad practice, because in general it prevents return value optimization. Shouldn't the identifier of a return statement be treated as rvalue first to satisfy conversions?
Is this code valid and which compiler is right here?
g++, c++14: compiles: http://coliru.stacked-crooked.com/a/f25ae94e8ca9c5c8
g++-4.8, c++11: does not compile: http://coliru.stacked-crooked.com/a/0402e3ebf97fd0e7
clang++, c++14: does not compile: http://coliru.stacked-crooked.com/a/682d8ca93d3e2f6a
Shouldn't the identifier of a return statement be treated as rvalue first to satisfy conversions?
Yes and no. From [class.copy], as a result of CWG 1579 (the wording here is copied from C++17, although it's the same in C++14. I find the bullets easier to read than the earlier grammar choice that would make James Joyce blush... ):
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.
The first bullet applies here, so we first do overload resolution as if foo was an rvalue. This gets is to the Other(Foo ) constructor by way of Foo(Foo&& ).
The first parameter of Other(Foo ) is not an rvalue reference, so we should do overload resolution again considering the foo as an lvalue, which fails. This seems like an unnecessary restriction, but I'd call clang correct here. If you change the constructor to Other(Foo&& ), clang accepts it.
Barry's fine answer covers the standard rules, but I have a practical suggestion:
and could only be fixed by adding std::move to the return statement which is considered bad practice, because in general it prevents return value optimization.
Your worry is unjustified. NRVO does not apply to conversions anyway, and RVO on the result of the move is allowed. The explicit move is fine.

Copy semantics for C++ unique pointer

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.

copy elision: move constructor not called when using ternary expression in return statement?

Consider the following example:
#include <cstdio>
class object
{
public:
object()
{
printf("constructor\n");
}
object(const object &)
{
printf("copy constructor\n");
}
object(object &&)
{
printf("move constructor\n");
}
};
static object create_object()
{
object a;
object b;
volatile int i = 1;
// With #if 0, object's copy constructor is called; otherwise, its move constructor.
#if 0
if (i)
return b; // moves because of the copy elision rules
else
return a; // moves because of the copy elision rules
#else
// Seems equivalent to the above, but behaves differently.
return i ? b : a; // copies (with g++ 4.7)
#endif
}
int main()
{
auto data(create_object());
return 0;
}
And consider this bit from the C++11 Working Draft, n3337.pdf, 12.8 [class.copy], point 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. 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 ]
Thus, if we use #if 1 in the example, the move constructor is first tried when returning the object, and then the copy constructor. Since we have a move constructor, it is used instead of the copy constructor.
In the last return statement in create_object() however, we've observed that the move constructor is not used. Is or is this not a violation of the language rules? Does the language require that the move constructor is used in the last return statement?
The specification for the conditional operator is so complicated it is scary. But I believe that your compiler is correct in its behavior. See 5.16 [expr.cond]/p4:
If the second and third operands are glvalues of the same value
category and have the same type, the result is of that type and value
category ...
Also see 12.8 [class.copy], p31, b1 which describes when copy elision is allowed:
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 ...
The expression is not the name of an automatic object, but is a conditional expression (and that conditional expression is an lvalue). So copy elision is not allowed here, and there is nothing else that says that the lvalue expression can pretend to be an rvalue for overload resolution.