Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Using Auto for Declaring Variables of Move-only Types

0.00/5 (No votes)
26 Sep 2014CPOL2 min read 10.6K  
This post demonstrates how to use auto with move-only types.

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:

C++
// Bad: The variable name is surrounded with "junk".
std::vector<std::string> myVariable { CreateStringVector(input1, input2) };

// Good: The variable name is always on the right side of "=".
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:

C++
#include <atomic>
#include <iostream>

int main()
{
    auto Counter = std::atomic<int>{22}; // Error: Won't compile.
    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:

C++
#include <atomic>
#include <iostream>

int main()
{
    auto && Counter = std::atomic<int>{22}; // OK
    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:

C++
#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:

C++
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:

C++
#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:

C++
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:

C++
#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; // Danger: Returning reference to temporary
}

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 :) :

C++
#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:

C++
MoveOnly::MoveOnly()
MoveOnly::MoveOnly(&&)
MoveOnly::~MoveOnly()
MoveOnly::~MoveOnly()

So, local variables with move-only types can be declared using “auto &&”.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)