Missing automatic move of returned local variable - c++

I'm running into an issue where under a specific set of conditions, a local variable being returning by a function is copied instead of moved. So far, it seems like it needs to meet the following:
The returned variable has some usage in the function. I'm assuming otherwise the whole copy/move is ellided.
The returned type is using a perfect forwarding-style constructor. From this answer (Usage of std::forward vs std::move) I learned that this style of constructor has some different deduction rules.
Be compiled in gcc before 8.0. Compiling under clang (and apparently gcc 8.0+, thanks PaulMcKenzie for the comment) produces the results I expect, however I'm not free to change the compiler being used in the larger project.
Here's some minimum reproduction:
#include <iostream>
#include <type_traits>
// Test class to print copies vs. moves.
class Value
{
public:
Value() : x(0) {}
Value(Value&& other) : x(other.x)
{
std::cout << "value move" << std::endl;
}
Value(const Value& other) : x(other.x)
{
std::cout << "value copy" << std::endl;
}
int x;
};
// A container class using a separate lvalue and rvalue conversion constructor.
template<typename T>
class A
{
public:
A(const T& v) : data_(v)
{
std::cout << "lvalue conversion" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
A(T&& v) : data_(std::move(v))
{
std::cout << "rvalue conversion" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
T data_;
};
// A container class using a single perfect forwarding constructor.
template<typename T>
class B
{
public:
template <typename U>
B(U&& v) : data_(std::forward<U>(v))
{
std::cout << "template conversion" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
T data_;
};
// Get a Value rvalue.
Value get_v()
{
Value v;
v.x = 10; // Without this things get ellided.
return v;
}
// Get an A<Value> rvalue.
A<Value> get_a()
{
Value v;
v.x = 10; // Without this things get ellided.
return v;
}
// Get a B<Value> rvalue.
B<Value> get_b()
{
Value v;
v.x = 10; // Without this things get ellided.
return v;
}
int main()
{
Value v = Value();
std::cout << "--------\nA" << std::endl;
std::cout << "lvalue" << std::endl;
A<Value> a0(v);
std::cout << a0.data_.x << std::endl;
std::cout << "rvalue" << std::endl;
A<Value> a1(get_v());
std::cout << a1.data_.x << std::endl;
std::cout << "get_a()" << std::endl;
std::cout << get_a().data_.x << std::endl;
std::cout << "--------\nB" << std::endl;
std::cout << "lvalue" << std::endl;
B<Value> b0(v);
std::cout << b0.data_.x << std::endl;
std::cout << "rvalue" << std::endl;
B<Value> b1(get_v());
std::cout << b1.data_.x << std::endl;
std::cout << "get_b()" << std::endl;
std::cout << get_b().data_.x << std::endl;
return 0;
}
Under gcc this produces:
--------
A
lvalue
value copy
lvalue conversion
A<T>::A(const T&) [with T = Value]
0
rvalue
value move
rvalue conversion
A<T>::A(T&&) [with T = Value]
10
get_a()
value move <---- Works with separate constructors.
rvalue conversion
A<T>::A(T&&) [with T = Value]
10
--------
B
lvalue
value copy
template conversion
B<T>::B(U&&) [with U = Value&; T = Value]
0
rvalue
value move
template conversion
B<T>::B(U&&) [with U = Value; T = Value]
10
get_b()
value copy <---- Not what I expect!
template conversion
B<T>::B(U&&) [with U = Value&; T = Value]
10
For completeness, clang gives:
--------
A
lvalue
value copy
lvalue conversion
A<Value>::A(const T &) [T = Value]
0
rvalue
value move
rvalue conversion
A<Value>::A(T &&) [T = Value]
10
get_a()
value move
rvalue conversion
A<Value>::A(T &&) [T = Value]
10
--------
B
lvalue
value copy
template conversion
B<Value>::B(U &&) [T = Value, U = Value &]
0
rvalue
value move
template conversion
B<Value>::B(U &&) [T = Value, U = Value]
10
get_b()
value move <---- Like this!
template conversion
B<Value>::B(U &&) [T = Value, U = Value]
10
I have two questions:
Is this allowed behavior by gcc?
Is there any way to force move behavior here through changing the implementation of A/B? It seems any time you have a templated function parameter being taken as && it will trigger the special rules for perfect forwarding, so if I try to provide two constructors as in the A example, one taking const U& and one taking U&&, it won't avoid the problem as long as they have other templating in place.

Related

What causes a move assignment operator to not be called?

I'm working on my own smart pointer and I ran into some weird problems. The move assignment operator was not being called. So I wrote a test class and was able to reproduce the issue. The move assignment operator is not called but a copy assignment occurs (even when there is no copy assignment operator).
This is my test class
#include <utility>
#include <iostream>
struct tag_t {};
constexpr tag_t tag {};
template <typename T>
struct Foo {
Foo() noexcept
: val{} {
std::cout << "Default construct\n";
}
template <typename U>
Foo(tag_t, const U &val) noexcept
: val{val} {
std::cout << "Construct " << val << '\n';
}
~Foo() noexcept {
std::cout << "Destruct " << val << '\n';
}
template <typename U>
Foo(Foo<U> &&other) noexcept
: val{std::exchange(other.val, U{})} {
std::cout << "Move construct " << val << '\n';
}
template <typename U>
Foo &operator=(Foo<U> &&other) noexcept {
std::cout << "Move assign " << other.val << '\n';
val = std::exchange(other.val, U{});
return *this;
}
T val;
};
These are the tests
int main() {
{
Foo<int> num;
std::cout << "Value " << num.val << '\n';
num = {tag, 5};
std::cout << "Value " << num.val << '\n';
}
std::cout << '\n';
{
Foo<int> num;
std::cout << "Value " << num.val << '\n';
num = Foo<int>{tag, 5};
std::cout << "Value " << num.val << '\n';
}
return 0;
}
After running the tests, I get these results
Default construct
Value 0
Construct 5
Destruct 5
Value 5
Destruct 5
Default construct
Value 0
Construct 5
Move assign 5
Destruct 0
Value 5
Destruct 5
What baffles me is the output of the first test. The move assignment operator is not called but a copy assignment takes place. This results in 5 being destroyed twice. Not ideal when you're trying to make a smart pointer!
I'm compiling with Apple Clang with optimizations disabled. Can someone explain my observations? Also, how do I ensure that the move assignment operator is called in the first test?
template <typename U>
Foo &operator=(Foo<U> &&other) noexcept;
this cannot be called by ={ }.
Instead, Foo& operator=(Foo&&)noexcept is called.
Template methods are never special member functions. Explicitly default, delete or implement them.

templated constructor loses rvalue

I am using a templated constructor in one of my classes
Could anybody tell me why this code compiles with no warnings or errors and yet one line of code (create test with default foo rvalue) just disappears!
#include <iostream>
class Foo
{
int _value;
public:
Foo()
:_value(0)
{
std::cout << __PRETTY_FUNCTION__ << "value[" << _value << "]" << std::endl;
}
Foo(int value)
:_value(value)
{
std::cout << __PRETTY_FUNCTION__ << "value[" << _value << "]" << std::endl;
}
Foo(Foo&& foo) = delete;
Foo(const Foo& foo) = delete;
friend std::ostream& operator << (std::ostream& output, const Foo& foo)
{
output << foo._value;
return output;
}
};
class Test
{
public:
template <typename Type>
Test(Type&& value)
{
std::cout << __PRETTY_FUNCTION__ << " with value[" << value << "]" << std::endl;
}
template <typename Type>
void fn(Type&& value)
{
std::cout << __PRETTY_FUNCTION__ << " with value[" << value << "]" << std::endl;
}
};
int main()
{
std::cout << "//----- test fn with foo rvalue ---------------" << std::endl;
Test test3(3);
test3.fn(Foo());
std::cout << "//----- test with int rvalue ------------------" << std::endl;
Test test4(1+3);
std::cout << "//----- create test with default foo rvalue ---" << std::endl;
Test test5(Foo());
std::cout << "//----- create test with foo rvalue -----------" << std::endl;
Test test7 (Foo(1+6));
std::cout << "//----- create test with moved foo rvalue -----" << std::endl;
Test test8(std::move(Foo()));
return 0;
}
This produces the following result
//----- test fn with foo rvalue ---------------
Test::Test(Type&&) [with Type = int] with value[3]
Foo::Foo()value[0]
void Test::fn(Type&&) [with Type = Foo] with value[0]
//----- test with int rvalue ------------------
Test::Test(Type&&) [with Type = int] with value[4]
//----- create test with default foo rvalue ---
//----- create test with foo rvalue -----------
Foo::Foo(int)value[7]
Test::Test(Type&&) [with Type = Foo] with value[7]
//----- create test with moved foo rvalue -----
Foo::Foo()value[0]
Test::Test(Type&&) [with Type = Foo] with value[0]
I am using g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2 with std=c++1y
If it helps, I added a line at the end
std::cout << test5;
And the compiler produced a warning
warning: the address of ‘Test test5(Foo (*)())’ will always evaluate as ‘true’ [-Waddress]
Test test5(Foo());
It's function declaration, not object creation. For create object you can use for example one of the following
Test test5((Foo()));
Test test5 = Test(Foo());
Test test5{Foo()};

const value and RVO

Say I have this function:
template <class A>
inline A f()
{
A const r(/* a very complex and expensive construction */);
return r;
}
Is it a good idea to declare r const, since a const variable cannot be moved? Note that the returned value is not const. The qualm I am grappling is, that r truly is const, but it may not be a good idea to declare it as such. Yet the qualifier should be helping the compiler generate better code.
As demonstrated here, NRVO elides the copy of r implied by the line return r;
#include <iostream>
struct A {
const char* name;
A( const char* name_ ):name(name_) { std::cout << "created " << name << "\n"; }
A(A const&){ std::cout << "copied " << name << "\n"; }
A(A &&){ std::cout << "moved " << name << "\n"; }
};
A f() {
std::cout << "start of f()\n";
A const r("bob");
std::cout << "body of f()\n";
return r;
}
int main() {
A x = f();
}
And the copy in main is also elided.
If you block NRVO and RVO in some other way (for instance using the flag -fno-elide-constructors when compiling with GCC), the const can cause your object to be copied instead of moved. You can see this if we remove the copy constructor from A:
#include <iostream>
struct A {
const char* name;
A( const char* name_ ):name(name_) { std::cout << "created " << name << "\n"; }
//A(A const&){ std::cout << "copied " << name << "\n"; }
A(A &&){ std::cout << "moved " << name << "\n"; }
};
A f() {
std::cout << "start of f()\n";
A const r("bob");
std::cout << "body of f()\n";
return r;
}
int main() {
A x = f();
}
the code no longer compiles. While the copy constructor isn't executed so long as NRVO occurs, its existence is required by your const local variable.
Now, NRVO requires a few things, such as a single variable which is returned along every single execution path of the function in question: if you ever "abort" and do a return A(), NRVO is blocked, and your const local variable suddenly forces a copy at all return sites.
If class A is under your control, and you want to return const objects by move, you can do
mutable bool resources_were_stolen = false;
and set that to true in a const move constructor
A(const A&& other) { ...; other.resources_were_stolen = true; }
~A() { if (!resources_were_stolen) ... }
Actually, the destructor probably would become if (resources_were_stolen) some_unique_ptr.release();, using the fact that objects lose their const-ness during construction and destruction.

default behaviour for (bool) cast

classes in the stl, such as unique_ptr will occasionally show examples such as:
// unique_ptr constructor example
#include <iostream>
#include <memory>
int main () {
std::default_delete<int> d;
std::unique_ptr<int> u1;
std::unique_ptr<int> u2 (nullptr);
std::unique_ptr<int> u3 (new int);
std::unique_ptr<int> u4 (new int, d);
std::unique_ptr<int> u5 (new int, std::default_delete<int>());
std::unique_ptr<int> u6 (std::move(u5));
std::unique_ptr<void> u7 (std::move(u6));
std::unique_ptr<int> u8 (std::auto_ptr<int>(new int));
std::cout << "u1: " << (u1?"not null":"null") << '\n';
std::cout << "u2: " << (u2?"not null":"null") << '\n';
std::cout << "u3: " << (u3?"not null":"null") << '\n';
std::cout << "u4: " << (u4?"not null":"null") << '\n';
std::cout << "u5: " << (u5?"not null":"null") << '\n';
std::cout << "u6: " << (u6?"not null":"null") << '\n';
std::cout << "u7: " << (u7?"not null":"null") << '\n';
std::cout << "u8: " << (u8?"not null":"null") << '\n';
*emphasized text* return 0;
}
The line:
std::cout << "u1: " << (u1?"not null":"null") << '\n';
shows the unique_ptr u1 being directly cast to false if it is tracking a null pointer.
I have seen this behaviour used in other custom classes. How is this managed and what operator decides whether a direct cast to bool such as this returns true or false?
It is implemented as a member conversion operator of the form explicit operator bool() const;. Whether it returns true or false is implemented in the logic of the class itself. For example, this class has an bool conversion operator that returns true if it's data member has value 42, and false otherwise:
struct Foo
{
explicit operator bool() const { return n==42; }
int n;
};
#include <iostream>
int main()
{
Foo f0{12};
Foo f1{42};
std::cout << (f0 ? "true\n" : "false\n");
std::cout << (f1 ? "true\n" : "false\n");
}
operator bool();
It's a standard cast-operator to type bool.
Here's an example of how to use it:
class Checked
{
bool state;
public:
Checked(bool _state) : state(_state) { }
explicit operator bool() const {
return state;
}
};
Checked c (true);
if (c)
cout << "true";
Note the explicit keyword. It appeared in C++11 and allows safe conversion of the class to bool, which happens, in short, in the logical context such as if(), while() etc. Without the explicit, bad things could happen, as there's an implicit conversion of bool to numeric types. In C++03 and older it's safer to overload operator ! (), which then is tested as if (!!c).
A conversion operator is used for this functionality:
operator bool(){ return false; }
In C++11 you can also make them explicit
explicit operator bool(){ return true; }
Implicit conversion operators are an error prone endeavour. Consider if unique_ptr had an implicit conversion operator to bool, you could do nonsensical things like...
std::unique_ptr<Thing> x;
int y = 7 + x;
In the above case y would equal 7 + (0 if x is null or 1 if x is non-null)

Is type checking for references in a template class less strict?

The following is allowd in C++ through promotion:
int ivalue = true;
bool bvalue = 1;
Thats okay. And this is not allowed, through type-checking:
int& ivalue = false;
bool& bvalue = 0;
Thats okay.
Look on this, from Wikipedia.
http://en.wikipedia.org/wiki/Property_(programming)#C.2B.2B
#include <iostream>
template <typename T> class property {
T value;
public:
T & operator = (const T &i) {
::std::cout << "T1: " << i << ::std::endl;
return value = i;
}
// This template class member function template serves the purpose to make
// typing more strict. Assignment to this is only possible with exact identical
// types.
template <typename T2> T2 operator = (const T2 &i) {
::std::cout << "T2: " << i << ::std::endl;
T2 &guard = value;
return value = i;
throw guard; // Never reached.
}/**/
operator T const & () const {
return value;
}
};
struct Bar {
// Using the property<>-template.
property <bool> alpha;
property <unsigned int> bravo;
};
int main () {
Bar bar;
bar.alpha = true;
bar.bravo = true; // This line will yield a compile time error
// due to the guard template member function.
::std::cout << foo.alpha << ", "
<< foo.bravo << ", "
<< bar.alpha << ", "
<< bar.bravo
<< ::std::endl;
bool bvar = 22;
int ivar = true;
//int &newvar = bvar;
print(bvar);
::std::cout << bvar << " and " << ivar << "\n";
return 0;
}
I think with using of templates the type-checking for references get lost? Am I right?
No, type conversions and reference binding rules are the same whether or not there's a template involved. You can bind a temporary (such as the one you need when the referee is a different type to the reference) to a const reference, but not to a non-const reference.
That is why your second pair of examples fails to compile, and also why the template operator= fails to compile in the larger example when the argument type doesn't match the template parameter. In both cases, the code tries to create a temporary through type-conversion and bind it to a non-const reference.