calynr
calynr

Reputation: 1282

Variadic template parameter pack deduction failure

I am experimenting with metaprogramming. While I was trying to concat variadic types with std::tuple I faced an issue.

#include <tuple>

template<typename... Args , typename... TupleArgs>
void f( Args&&... , const std::tuple<TupleArgs...>& )
{   }

template<typename... Args , typename... TupleArgs>
void f2( const std::tuple<TupleArgs...>& , Args&&... )
{   }

int main(){
    auto t = std::make_tuple( 1 , 2 , 2.0 , 3.0f );

    // f ( 1 , 2 , t ); // deduction/substition failed
    f2( t , 1 , 2 ); // Ok, no problem
} 

see online

I intuitively expect both functions compile without an error, but it seems compiler complains about it.

Can someone explain why does f give deduction/substitution error but f2 not ?

Is there a workaround ( i am sure there is ) ?

Compiler : x86-x64 GCC 9.2 with -std=c++17 option

Upvotes: 1

Views: 400

Answers (2)

Jarod42
Jarod42

Reputation: 217085

Issue with

template<typename... Args, typename... TupleArgs>
void f( Args&&... , const std::tuple<TupleArgs...>& );

is that Args&&... is non deducible, as not the last parameter.

Possible workaround is to provide the template:

f<int, int> ( 1 , 2 , t ); (then Args&&... should probably be Args...).

Another work around is to have it at last parameter, and use SFINAE to allow only tuple as last argument:

// Traits to detect std::tuple
template <typename> struct is_tuple : std::false_type{};
template <typename...Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type{};

// Helper function to retrieve tuple and argument
template <typename Tuple, std::size_t...Is>
void f3_impl(Tuple&& tuple, std::index_sequence<Is...>)
{
    f2(std::get<sizeof...(Is)>(std::forward<Tuple>(tuple)),
       std::get<Is>(std::forward<Tuple>(tuple))...);
}

template<typename... Args>
std::enable_if_t<sizeof...(Args) != 0
             && (is_tuple<std::decay_t<Args>>::value, ...)> /* Only use LAST value */
f3( Args&&... args)
{
    f3_impl(std::forward_as_tuple(std::forward<Args>(args)...),
            std::make_index_sequence<sizeof...(Args) - 1>());
}

Demo

Upvotes: 1

Alex
Alex

Reputation: 877

f fails because the compiler can't tell if your tuple argument t that you pass to it is part of the Args&&... list or is the const std::tuple<TupleArgs...>& instance (which in this case is a SINGLE tuple that happens to have a variadic type).

f2's first argument is actually a single type (tuple) followed by a variadic parameter pack Args&&..., so any parameter that you pass after the first parameter obviously belongs to Args&&.... Note that I'm referring to the parameters of the actual function, not the template declaration. The function prototypes are what is causing this behavior.

If you want to have two parameter packs in a function parameter list, then you can do a few tricks, like put BOTH into tuples, or put one in a template template parameter (nested templates).

I've included two example solutions. I altered f to use two tuples, and I created a new function f3 that uses a template template parameter.

#include <tuple>

template<typename... Args, typename... TupleArgs>
void f(const std::tuple<Args...>&, const std::tuple<TupleArgs...>&)
{   }

template<typename... TArgs, template <typename...> class T, typename... TupleArgs>
void f3(T<TArgs...>&, const std::tuple<TupleArgs...>&)
{   }

template<typename... Args, typename... TupleArgs>
void f2(const std::tuple<TupleArgs...>&, Args&&...)
{   }

int main() {
    auto t = std::make_tuple(1, 2, 2.0, 3.0f);
    auto t2 = std::make_tuple(1, 2);

     f( t2 , t ); // FIXED
    f2(t, 1, 2); // Ok, no problem
    f3(t2, t);
}

Upvotes: 3

Related Questions