Search code examples
c++c++17variadic-templates

rvalue reference not working when parameter is a parameter pack


When I make an rvalue from a template parameter pack it does not compile, but if it is a 'simple' template parameter it compiles fine.

In this code for_each_in_tup1 compiles fine, but for_each_in_tup3 does not. I do not understand why this will not compile, but both GCC 9.2 and VC v142 agrees that it is wrong.

Why is this not valid syntax?

This example:

#include <tuple>
using namespace std;

template< typename Tuple, size_t N = 0>
void for_each_in_tup1( Tuple&& tup ) {
    if constexpr(N < tuple_size<decay_t<Tuple>>::value)
        for_each_in_tup1<Tuple, N + 1>(forward<Tuple>(tup));
}

template< template<typename...> typename Tuple, typename... Ts>
void for_each_in_tup2( const Tuple<Ts...>& tup) {
}

template< template<typename...> typename Tuple, typename... Ts>
void for_each_in_tup3( Tuple<Ts...>&& tup) {
}

void test_lazy() {
    tuple<uint32_t, uint32_t> tup;
    for_each_in_tup1(tup);
    for_each_in_tup2(tup);
    for_each_in_tup3(tup);
}

fails with:

<source>:20:22: error: cannot bind rvalue reference of type 'std::tuple<unsigned int, unsigned int>&&' to lvalue of type 'std::tuple<unsigned int, unsigned int>'
   20 |     for_each_in_tup3(tup);
      |                      ^~~

<source>:15:39: note:   initializing argument 1 of 'void for_each_in_tup3(Tuple<Ts ...>&&) [with Tuple = std::tuple; Ts = {unsigned int, unsigned int}]'
   15 | void for_each_in_tup3( Tuple<Ts...>&& tup) {
      |                        ~~~~~~~~~~~~~~~^~~

godbolt


Solution

  • In for_each_in_tup1 you are using forwarding reference:

    template< typename Tuple, size_t N = 0>
    void for_each_in_tup1( Tuple&& tup ) 
    

    when you pass lvalue, Tuple is deduced to be Tuple&, and after reference collpasing you will get for_each_in_tup1(Tuple&). lvalue can be bound to lvalue reference, that is why this code works. If you passed rvalue into for_each_in_tup1, parameter would be Tuple&&, and it could accept only rvalues.

    In for_each_in_tup3 there is normal rvalue reference, which can bind only rvalues. To call for_each_in_tup3 you have to cast tup to rvalue for example by std::move:

    for_each_in_tup3(std::move(tup));