c++c++14undefined-behaviormove-semanticsstdtuple

How to move std::tuple elements out of a class object?


Problem:

I would like to move the elements out of a class member tuple-type variable, but I wonder whether the following code (near-minimal example, being limited to C++14) is allowed:

#include <tuple>
#include <iostream>

// For testing, assume a move-only class template:
enum class Index { idx1, idx2, idx3 };
template <Index I>
struct B {
    ~B()                    = default;
    B(B&&)                  = default;
    B& operator= (B&&)      = default;
    B(B const&)             = delete;
    B& operator= (B const&) = delete;
    
    Index getIndex() { return I; }
};

// Class to consider
template <Index... Is_>
class A
{
    public:
        A(B<Is_>&&... _b) : m_tuple{std::move(_b)...} {}
        
        template <Index... Js_>
        std::tuple<B<Js_>...> moveBs() &&
        {
            return { std::move(*this).template moveB<Js_>()... };
        }

    private:
        template <Index J_>
        B<J_> moveB() &&
        {
            // Attention: The entire tuple shall be moved, not the return
            //            value(s) of std::get<>() - see answer to question
            //            77664807: "How does std::forward work in the
            //            context of a fold expression?",  thanks to
            //            'user12002570' for pointing this out.
            return std::get<B<J_>>(std::move(m_tuple));
        }
        std::tuple<B<Is_>...>   m_tuple;
};

// Output only as an example, to try out
void print(Index _i)
{ 
    std::cout << ( (_i == Index::idx1) ? "Index::idx1" 
                 : (_i == Index::idx2) ? "Index::idx2"
                 : (_i == Index::idx3) ? "Index::idx3" 
                 : "unknown!")
              << std::endl;
}

int main()
{
    A<Index::idx1,
      Index::idx2,
      Index::idx3> a{   B<Index::idx1>{}, 
                        B<Index::idx2>{}, 
                        B<Index::idx3>{}  };
    B<Index::idx1> b1{};
    B<Index::idx2> b2{};

    // Call to consider
    std::tie(b1, b2) = std::move(a).moveBs<Index::idx1,
                                           Index::idx2>();
    
    // Usage of objects b1,b2 - verify the result of the previous call
    print(b1.getIndex());
    print(b2.getIndex());
}

The question referred to in the example code is found here

Clang and GCC compilers accept this code, but moving several times from object a, as it is done through moveBs<Js_>(), feels illegal.

Compiler options used: --std=c++14 -O3 -Wall -Wextra -Wshadow -Wconversion -Wpedantic -Werror.

Questions:

  1. Is it allowed to move from a member like m_tuple if that is done from a &&-qualified method like moveB<>()?

  2. Is it allowed to "invoke" std::move(*this) multiple times, as it seems to be done in the parameter pack expansion in moveBs<>()?


Solution

  • Is it allowed to move from a member like m_tuple if that is done from a &&-qualified method like moveB<>()?

    Yes.

    Is it allowed to "invoke" std::move(*this) multiple times, as it seems to be done in the parameter pack expansion in moveBs<>()?

    Yes.

    What you do not want to do is to move multiple times from a resource that may end up in a valid but indeterminate state. Your code doesn't do that.