Search code examples
c++rvalue-reference

C++11 field of rvalue is a rvalue


I'm trying to setup a template to swap arrays in order to be used in move constructors for many classes:

template <class T> void tools_swap_array(T* & a, T* & b) noexcept
{
    auto tmp(a);
    a = b;
    b = tmp;
}

now I want to use it in move constructor and move assignment operators through a common method called move_from():

void label::move_from(label && ref) noexcept  
{
    tools_swap_array((char *)val, (char*)ref.val);
}

where val is a field of class label:

class label
{
public:
    // [...]
    label(label && ref) { move_from(std::move(ref)); };
    label & operator = (label && ref) { move_from(std::move(ref)); return *this; };
    // [...]
private:
    char val[LABEL_SIZE];
};

But when I run the compiler, it fails in move_from() telling that ref.val is an rvalue!

error: invalid initialization of non-const reference of type 'char*&'
from an rvalue of type 'char*'  
tools_swap_array((char *)val, (char*)ref.val);
                                            ^

I thought that "label && ref" would be an lvalue inside move_from() and so would be "ref.val". So, do I still misunderstand C++11 reference to rvalue or is this a compiler bug?

Thanks for any hint!


Solution

  • The correct way to implement this is to not write your own swap function at all. std::swap() has an overload for fixed-sized arrays in C++11 and later, use that instead, eg:

    #include <utility>
    
    void label::move_from(label && ref) noexcept  
    {
        std::swap(val, ref.val);
    }
    

    If you really want to implement an array swapping function manually, you need to pass references to the actual arrays, instead of passing pointers to them (swapping just the pointers does not swap the array elements at all), eg:

    #include <algorithm>
    
    template <class T, std::size_t N>
    void tools_swap_array(T (&a)[N], T (&b)[N]) noexcept
    {
        T tmp[N];
        std::copy_n(a, N, tmp);
        std::copy_n(b, N, a);
        std::copy_n(tmp, N, b);
    }
    

    Or:

    #include <utility>
    
    template <class T, std::size_t N>
    void tools_swap_array(T (&a)[N], T (&b)[N]) noexcept
    {
        for(std::size_t i = 0; i < N; ++i)
            std::swap(a[i], b[i]);
    }
    

    Or (this is essentially what std::swap() does):

    #include <algorithm>
    
    template <class T, std::size_t N>
    void tools_swap_array(T (&a)[N], T (&b)[N]) noexcept
    {
        std::swap_ranges(a, a+N, b);
    }
    

    Either way, then you can do this:

    void label::move_from(label && ref) noexcept  
    {
        tools_swap_array(val, ref.val);
    }