This post demonstrates how to use auto with move-only types.
In Declaration and initialization with auto, we showed that using auto for local declarations makes the code safer and more readable. It avoids writing declarations where we easily loose sight of the variable name. Like the first declaration in this example:
std::vector<std::string> myVariable { CreateStringVector(input1, input2) };
auto myVariable = std::vector<std::string>{ CreateStringVector(input1, input2) };
Unfortunately, if you want to declare a variable of a move-only type with auto, then you might try this:
#include <atomic>
#include <iostream>
int main()
{
auto Counter = std::atomic<int>{22}; std::cout << Counter << std::endl;
return 0;
}
But then, you get this compilation error (clang 3.5.0):
prog.cc:6:10: error: call to implicitly-deleted copy constructor of 'std::__1::atomic<int>'
auto Counter = std::atomic<int>{22};
^ ~~~~~~~~~~~~~~~~~~~~
/usr/local/libcxx-3.5/include/c++/v1/atomic:730:7: note: copy constructor of 'atomic<int>'
is implicitly deleted because base class '__atomic_base<int>' has a deleted copy constructor
: public __atomic_base<_Tp>
^
/usr/local/libcxx-3.5/include/c++/v1/atomic:649:7: note: copy constructor of '__atomic_base<int,
true>' is implicitly deleted because base class '__atomic_base<int,
false>' has a deleted copy constructor
: public __atomic_base<_Tp, false>
^
/usr/local/libcxx-3.5/include/c++/v1/atomic:634:5:
note: '__atomic_base' has been explicitly marked deleted here
__atomic_base(const __atomic_base&) = delete;
^
1 error generated.
You get the error because the move-only type (std::atomic
in this case) has a deleted copy constructor.
Luckily, all is not lost on our quest for Almost Always Auto. The solution is to use Universal References:
#include <atomic>
#include <iostream>
int main()
{
auto && Counter = std::atomic<int>{22}; std::cout << Counter << std::endl;
return 0;
}
The above code compiles and runs.
To see what’s happening, let’s define our own move-only type:
#include <iostream>
class MoveOnly
{
public:
MoveOnly() { std::cout << "MoveOnly::MoveOnly()" << std::endl; }
MoveOnly(const MoveOnly &) = delete;
MoveOnly(MoveOnly &&) { std::cout <<
"MoveOnly::MoveOnly(&&)" << std::endl; }
MoveOnly & operator=(const MoveOnly &) = delete;
MoveOnly & operator=(MoveOnly &&) = delete;
~MoveOnly() { std::cout << "MoveOnly::~MoveOnly()" << std::endl; }
void Func() {}
};
int main()
{
auto && moveOnlyRValue = MoveOnly{};
moveOnlyRValue.Func();
return 0;
}
Below is the output of the above code. No surprises or extra work, just as we’d want:
MoveOnly::MoveOnly()
MoveOnly::~MoveOnly()
To see the type that the compiler deduced for our variables, we can use the “Type Displayer” trick by Scott Meyers:
#include <iostream>
class MoveOnly
{
public:
MoveOnly() { std::cout << "MoveOnly::MoveOnly()" << std::endl; }
MoveOnly(const MoveOnly &) = delete;
MoveOnly(MoveOnly &&) { std::cout <<
"MoveOnly::MoveOnly(&&)" << std::endl; }
MoveOnly & operator=(const MoveOnly &) = delete;
MoveOnly & operator=(MoveOnly &&) = delete;
~MoveOnly() { std::cout << "MoveOnly::~MoveOnly()" << std::endl; }
void Func() {}
};
template <typename T>
class TypeDisplayer;
int main()
{
auto && moveOnlyRValue = MoveOnly{};
auto td1 = TypeDisplayer<decltype(moveOnlyRValue)>{};
moveOnlyRValue.Func();
auto moveOnly = std::move(moveOnlyRValue);
auto td2 = TypeDisplayer<decltype(moveOnly)>{};
moveOnly.Func();
return 0;
}
This is what we get when we attempt to compile:
prog.cc:21:16: error: implicit instantiation of undefined template
'TypeDisplayer<MoveOnly &&>'
auto td1 = TypeDisplayer<decltype(moveOnlyRValue)>{};
^
prog.cc:16:7: note: template is declared here
class TypeDisplayer;
^
prog.cc:25:16: error: implicit instantiation of undefined template 'TypeDisplayer<MoveOnly>'
auto td2 = TypeDisplayer<decltype(moveOnly)>{};
^
prog.cc:16:7: note: template is declared here
class TypeDisplayer;
^
2 errors generated.
The compiler tells us that moveOnlyRValue
is deduced as “MoveOnly &&
” and moveOnly
is deduced as “MoveOnly
”.
Is it safe to do this? Is it safe to declare a move-only variable as “auto &&
”? What happens?
Well, our variable will be an rvalue
reference to a temporary object.
- The question is the same as “Is it safe to declare our variable with “auto &””
- Which is the same as “Is it safe to declare our variable with “Type &””
- Which is similar to “Is it safe to declare our variable with “const Type &””
And we do this later all the time to avoid unnecessary copies of temporaries:
const BigStuff & bigStuff = MakeBigStuff();
This causes trouble only if this reference outlives the object. For example, if we return it from a function, but luckily compilers already know about this pitfall and shout at us:
#include <iostream>
class MoveOnly
{
public:
MoveOnly() { std::cout << "MoveOnly::MoveOnly()" << std::endl; }
MoveOnly(const MoveOnly &) = delete;
MoveOnly(MoveOnly &&) { std::cout <<
"MoveOnly::MoveOnly(&&)" << std::endl; }
MoveOnly & operator=(const MoveOnly &) = delete;
MoveOnly & operator=(MoveOnly &&) = delete;
~MoveOnly() { std::cout << "MoveOnly::~MoveOnly()" << std::endl; }
void Func() const {}
};
MoveOnly & Shmunc()
{
auto && moveOnlyRValue = MoveOnly{};
return moveOnlyRValue; }
int main()
{
const auto & n = Shmunc();
n.Func();
return 0;
}
You get this warning if you enable warnings of your compiler, which you always should (e.g. -Wall
for gcc and clang):
prog.cc:18:12: warning: returning reference to local temporary object [-Wreturn-stack-address]
return moveOnlyRValue;
^~~~~~~~~~~~~~
prog.cc:17:13: note: binding reference variable 'moveOnlyRValue' here
auto && moveOnlyRValue = MoveOnly{};
^ ~~~~~~~~~~
1 warning generated.
The solution is not to return references to local temporaries :) :
#include <iostream>
class MoveOnly
{
public:
MoveOnly() { std::cout << "MoveOnly::MoveOnly()" << std::endl; }
MoveOnly(const MoveOnly &) = delete;
MoveOnly(MoveOnly &&) { std::cout <<
"MoveOnly::MoveOnly(&&)" << std::endl; }
MoveOnly & operator=(const MoveOnly &) = delete;
MoveOnly & operator=(MoveOnly &&) = delete;
~MoveOnly() { std::cout << "MoveOnly::~MoveOnly()" << std::endl; }
void Func() const {}
};
MoveOnly Hunc()
{
auto && moveOnlyRValue = MoveOnly{};
return std::move(moveOnlyRValue);
}
int main()
{
const auto & n = Hunc();
n.Func();
return 0;
}
We get this output:
MoveOnly::MoveOnly()
MoveOnly::MoveOnly(&&)
MoveOnly::~MoveOnly()
MoveOnly::~MoveOnly()
So, local variables with move-only types can be declared using “auto &&
”.
References
CodeProject