Search code examples
c++c++17variadic-templatesrvalue-referencefold-expression

Variadic template function rvalue parameters silently moved C++ to the function


The following code compiles and prints: move move. I would prefer that it didn't compile since merge takes rvalue references and I don't move t1 and t2 to it.

class A {
   public:
    A() = default;
    A(const A& other) { std::cout << "copy "; };
    A(A&& other) { std::cout << "move "; };
};

template <typename... TupleType>
auto merge(TupleType&&... tuples) {
    return std::tuple_cat(std::move(tuples)...);
}
int main() {
    std::tuple<int> t1{1};
    std::tuple<A> t2{A()};
    auto t3 = merge(t1, t2);
}

I'm not sure what happens here and why. Furthermore, I think this behavior is dangerous: I have no move in the call to merge, but t1 and t2 are moved from.

Why is this allowed and how can I make merge take only rvalue references?


Solution

  • For why is this possible, See Reference_collapsing.

    Now If you want to prevent your function from accepting lvalues, you can use the following code

    #include <tuple>
    #include <iostream>
    
    
    class A {
    public:
        A()  = default;
        A(const A& other) { std::cout << "\ncopy "; }
        A(A&& other)noexcept { std::cout << "\nmove "; }
    };
    template <typename... TupleType>
    auto merge(TupleType&... tuples) = delete;
    
    template <typename... TupleType>
    auto merge(TupleType&&... tuples) {
        return std::tuple_cat(std::forward<TupleType>(tuples)...);
    }
    
    int main() {
        std::tuple<int> t1{1};
        std::tuple<A> t2{A()};
        // auto t3 = merge(t1, t2);//won't compile
    
        //compiles and gives the desired behavior move move
        auto t4 = merge(std::make_tuple(1), std::make_tuple(A{}));
    }
    

    Live