Consider the following example:
class X {
public:
X() = default;
X(const X&) = default;
X(X&&) = delete;
};
X foo() {
X result;
return result;
}
int main() {
foo();
}
Clang and GCC disagree on whether this program is valid. GCC tries to call the move constructor when initializing the temporary during the call to foo(), which has been deleted leading to a compilation error. Clang handles this just fine, even with -fno-elide-constructors.
Can anyone explain why GCC is allowed to call the move constructor in this case? Isn't result an lvalue?
I'm gonna quote C++17 (n4659), since the wording there is the most clear, but previous revisions say the same, just less clearly to me. Here is [class.copy.elision]/3, emphasis mine:
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 ]
So this is why in fact both Clang and GCC will try to call the move c'tor first. The difference in behavior is because Clang adheres to the text in bold differently. Overload resolution happened, found a move c'tor, but calling it is ill-formed. So Clang performs it again and finds the copy c'tor.
GCC just stops in its tracks because a deleted function was picked in overload resolution.
I do believe Clang is correct here, in spirit if anything else. Trying to move the returned value and copying as a fallback is the intended behavior of this optimization. I feel GCC should not stop because it found a deleted move c'tor. Neither compiler would if it was a defaulted move c'tor that's defined deleted. The standard is aware of a potential problem in that case ([over.match.funcs]/8):
A defaulted move special function ([class.copy]) that is defined as
deleted is excluded from the set of candidate functions in all
contexts.
From [class.copy.elision]:
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 [...]
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.
In return result; we are precisely in the case mentioned in that paragraph - result is an id-expression naming an object with automatic storage duration declared in the body. So, we first perform overload resolution as if it were an rvalue.
Overload resolution will find two candidates: X(X const&) and X(X&&). The latter is preferred.
Now, what does it mean for overload resolution to fail? From [over.match]/3:
If a best viable function exists and is unique, overload resolution succeeds and produces it as the result. Otherwise overload resolution fails and the invocation is ill-formed.
X(X&&) is a unique, best viable function, so overload resolution succeeds. This copy elision context has one extra criteria for us, but we satisfy it as well because this candidate has as the type of its first parameter as (possibly cv-qualified) rvalue reference to X. Both boxes are checked, so we stop there. We do not go on to perform overload resolution again as an lvalue, we have already selected our candidate: the move constructor.
Once we selected it, the program fails because we're trying to call a deleted function. But only at that point, and not any earlier. Candidates are not excluded from overload sets for being deleted, otherwise deleting overloads wouldn't be nearly as useful.
This is clang bug 31025.
result is not an lvalue after returning from foo(), but a prvalue, so it is allowed to call the move constructor.
From CppReference:
The following expressions are prvalue expressions:
a function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++
It is possible that Clang detected that the return value is unused and threw it away directly, while GCC didn't.
Related
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.
Had a compliation issue recently, illustrated by this snippet:
struct Base
{
};
template<typename T>
struct A : Base
{
A(){}
A(Base&&) {}
};
A<int> foo()
{
A<double> v;
return v;
}
int main()
{
auto d = foo();
return 0;
}
Gcc says it's ok, but clang disagrees and says "candidate constructor not viable: no known conversion from 'A' to 'Base &&' for 1st argument A(Base&&) {}", see for yourself: https://godbolt.org/z/Y7mwnU
Would any of the kind readers be able to help with some standardese to support either point of view?
clang is correct here. Filed 87530.
The rule for return statements is [class.copy.elision]/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 ([stmt.return]) 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
if the operand of a throw-expression 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),
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 ]
Emphasis mine.
We meet the first bullet, we're returning an id-expression that names a non-volatile automatic object. So we perform overload resolution as if it were an rvalue. This overload resolution succeeds, there is a constructor that takes a Base&&. However, note the bolded part. The type of this parameter is not an rvalue reference to the object's type.
Hence, we try again consider the object as an lvalue. This overload resolution fails.
As a result, the program is ill-formed.
Consider the following:
struct X {
X() {}
X(X&&) { puts("move"); }
};
X x = X();
In C++14, the move could be elided despite the fact that the move constructor has side effects thanks to [class.copy]/31,
This elision of copy/move operations ... is permitted in the following circumstances ... 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
In C++17 this bullet was removed. Instead the move is guaranteed to be elided thanks to [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. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. — end
example ]
Thus far the facts I've stated are well-known. But now let's change the code so that it reads:
X x({});
In C++14, overload resolution is performed and {} is converted to a temporary of type X using the default constructor, then moved into x. The copy elision rules allow this move to be elided.
In C++17, the overload resolution is the same, but now [dcl.init]/17.6.1 doesn't apply and the bullet from C++14 isn't there anymore. There is no initializer expression, since the initializer is a braced-init-list. Instead it appears that [dcl.init]/(17.6.2) applies:
Otherwise, 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 (16.3.1.3),
and the best one is chosen through overload resolution (16.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.
This appears to require the move constructor to be called, and if there's a rule elsewhere in the standard that says it's ok to elide it, I don't know where it is.
As T.C. points out, this is in a similar vein to CWG 2327:
Consider an example like:
struct Cat {};
struct Dog { operator Cat(); };
Dog d;
Cat c(d);
This goes to 11.6 [dcl.init] bullet 17.6.2:
Otherwise, 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 (16.3.1.3 [over.match.ctor]), and the best one is chosen through overload resolution (16.3 [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.
Overload resolution selects the move constructor of Cat. Initializing the Cat&& parameter of the constructor results in a temporary, per 11.6.3 [dcl.init.ref] bullet 5.2.1.2. This precludes the possitiblity of copy elision for this case.
This seems to be an oversight in the wording change for guaranteed copy elision. We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.
What makes this the same underlying problem is that we have an initializer (in OP, {}, in this example, d) that's the wrong type - we need to convert it to the right type (X or Cat), but to figure out how to do that, we need to perform overload resolution. This already gets us to the move constructor - where we're binding that rvalue reference parameter to a new object that we just created to make this happen. At this point, it's too late to elide the move. We're already there. We can't... back up, ctrl-z, abort abort, okay start over.
As I mentioned in the comments, I'm not sure this was different in C++14 either. In order to evaluate X x({}), we have to construct an X that we're binding to the rvalue reference parameter of the move constructor - we can't elide the move at that point, the reference binding happens before we even know we're doing a move.
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.
I have this function:
fstream open_user_file() const
{
...
}
but my compiler complains about fstream copy-constructor being implicitly deleted. Given that the compiler performs RVO, why is the copy constructor chosen instead of the move constructor?
Otherwise, what's the best way to do this?
The currently accepted answer is just wrong.
When returning a local variable with automatic storage, of the same type as the declared return type of the function, then there is a two phase process going on:
fstream open_user_file() const
{
fstream f;
/*...*/
return f;
}
The selection of 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.
This means that if f is move constructible, that will be preferred (and possibly elided) for returning f. Else if f is copy constructible that will be done (and possibly elided) for returning f. Otherwise f can not be returned from this function and a compile-time error should result.
The only case in which:
return std::move(f);
should help is when the implementation is buggy. In a conforming implementation, fstream is move constructible and:
return f;
will be optimal. If f is not move constructible, then:
return std::move(f);
won't help in a conforming implementation. And if coded anyway in a conforming implementation will have the effect of a pessimization, in that it will inhibit RVO.
gcc 4.8 has not implemented movable streams (and streams are not copyable). And this is the source of your problem. In C++98, C++03, and gcc 4.8, streams are not returnable from functions. In C++11 they are.
An implementation may omit a copy operation resulting from a return statement, even if the copy constructor has side effects. In this case you may just have to explicitly move.
fstream open_user_file() const
{
fstream f;
/*...*/
return std::move(f);
}
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
object, even if the constructor selected for the copy/move operation and/or the destructor for the object
have side effects.
...
And this is where it says the copy constructor must be accessible:
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.