80

Function one() accepts one parameter pack. Function two() accepts two. Each pack is constrained to be wrapped in types A and B. Why is it impossible to instantiate two()?

template <typename T>
struct A {};

template <typename T>
struct B {};

template <typename... Ts>
void one(A<Ts> ...as) {
}

template <typename... Ts, typename... Us>
void two(A<Ts> ...as, B<Us> ...bs) {
}

int main() {
  auto a = A<int>();
  auto b = B<int>();

  // Just fine
  one();
  one(a);
  one(a, a);

  // All errors    
  two();
  two(a);
  two(a, b);
}

Tried with gcc and clang.

sam@wish:~/x/cpp$ gcc -std=c++0x variadic_templates.cpp 
variadic_templates.cpp: In function ‘int main()’:
variadic_templates.cpp:23:7: error: no matching function for call to ‘two()’
variadic_templates.cpp:23:7: note: candidate is:
variadic_templates.cpp:11:6: note: template<class ... Ts, class ... Us> void two(A<Ts>..., B<Us>...)
variadic_templates.cpp:24:8: error: no matching function for call to ‘two(A<int>&)’
variadic_templates.cpp:24:8: note: candidate is:
variadic_templates.cpp:11:6: note: template<class ... Ts, class ... Us> void two(A<Ts>..., B<Us>...)
variadic_templates.cpp:25:11: error: no matching function for call to ‘two(A<int>&, B<int>&)’
variadic_templates.cpp:25:11: note: candidate is:
variadic_templates.cpp:11:6: note: template<class ... Ts, class ... Us> void two(A<Ts>..., B<Us>...)
sam@wish:~/x/cpp$ clang -std=c++0x variadic_templates.cpp 
variadic_templates.cpp:23:3: error: no matching function for call to 'two'
  two();
  ^~~
variadic_templates.cpp:11:6: note: candidate function template not viable: requires at least 1 argument, but 0 were provided                                                                                                                 
void two(A<Ts> ...as, B<Us> ...bs) {}
     ^
variadic_templates.cpp:24:3: error: no matching function for call to 'two'                                                                                                                                                                   
  two(a);
  ^~~
variadic_templates.cpp:11:6: note: candidate function not viable: requires 0 arguments, but 1 was provided                                                                                                                                   
void two(A<Ts> ...as, B<Us> ...bs) {}
     ^
variadic_templates.cpp:25:3: error: no matching function for call to 'two'                                                                                                                                                                   
  two(a, b);
  ^~~
variadic_templates.cpp:11:6: note: candidate function not viable: requires 0 arguments, but 2 were provided                                                                                                                                  
void two(A<Ts> ...as, B<Us> ...bs) {}
     ^
3 errors generated.
Samuel Danielson
  • 5,231
  • 3
  • 35
  • 37
  • 1
    `A ...as` -- is this even legal as a parameter? – Xeo Mar 22 '12 at 22:31
  • 6
    How would the compiler know when one pack ends and the other begins? – ildjarn Mar 22 '12 at 22:32
  • @Xeo: It's legal AFAIK. Two compilers accept it. – Samuel Danielson Mar 22 '12 at 22:33
  • 4
    @ildjarn: Because one pack must have types wrapped in A while the other must have types wrapped in B. – Samuel Danielson Mar 22 '12 at 22:34
  • 4
    The committee considers (or at least did so, not sure about the current state) changing the rules regarding this so currently compiler implementations differ in the handling of them. For example, clang rejects a call to a `templatevoid f(A...a,B...b)` with arguments > 1 (because "A...a" is a non-deduced context since it is not the last function parameter pack and it cannot be "catch-all"-deduced to an empty pack because it is not a trailing template parameter pack of `f`) but GCC accepts it, giving `A` the empty pack list and `B` all incoming argument types. – Johannes Schaub - litb Mar 22 '12 at 22:36
  • @litb: Thanks! My version of clang rejects everything, which is probably the best option given the lack of standardization. I did find that my gcc accepts two(b), two(b, b), ... Looks like I need to read up on what constitutes a deducible (or deduced?) context. – Samuel Danielson Mar 22 '12 at 22:42
  • @Samuel yes for me clang also rejects when you give zero arguments (for the same reason). NVM my mentions about arguments > 1, I was confused. – Johannes Schaub - litb Mar 22 '12 at 23:00
  • 2
    Was there further clarification on this in C++14/17? – ThomasMcLeod Jan 01 '18 at 16:02

5 Answers5

56

Here is another way to have several parameters packs using template template parameters:

#include <iostream>

template <typename... Types>
struct foo {};

template < typename... Types1, template <typename...> class T
         , typename... Types2, template <typename...> class V
         , typename U >
void
bar(const T<Types1...>&, const V<Types2...>&, const U& u)
{
  std::cout << sizeof...(Types1) << std::endl;
  std::cout << sizeof...(Types2) << std::endl;
  std::cout << u << std::endl;
}

int
main()
{
  foo<char, int, float> f1;
  foo<char, int> f2;
  bar(f1, f2, 9);
  return 0;
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
Alexandre Hamez
  • 7,725
  • 2
  • 28
  • 39
21

I found one solution. Wrap each parameter pack in a Tuple. Use a struct for partial specialization. Here's a demo that forwards arguments to a functor by consuming one tuple as a list and accumulating another. Well, this one forwards by copying. Tuples are used in type deduction yet no tuples are used in function parameters, which I think is neat.

#include <iostream>
#include <tuple>

template < typename ... >
struct two_impl {};

// Base case
template < typename F,
           typename ...Bs >
struct two_impl < F, std::tuple <>, std::tuple< Bs... > >  {
  void operator()(F f, Bs... bs) {
    f(bs...);
  }
};

// Recursive case
template < typename F,
           typename A,
           typename ...As,
           typename ...Bs >
struct two_impl < F, std::tuple< A, As... >, std::tuple< Bs...> >  {
  void operator()(F f, A a, As... as, Bs... bs) {
    auto impl = two_impl < F, std::tuple < As... >, std::tuple < Bs..., A> >();
    impl(f, as..., bs..., a);
  }
};

template < typename F, typename ...Ts >
void two(F f, Ts ...ts) {
  auto impl = two_impl< F, std::tuple < Ts... >, std::tuple <> >();
  impl(f, ts...);
}

struct Test {
  void operator()(int i, float f, double d) {
    std::cout << i << std::endl << f << std::endl << d << std::endl;
  }
};

int main () {
  two(Test(), 1, 1.5f, 2.1);
}

Tuples are a very good compile time list.

Samuel Danielson
  • 5,231
  • 3
  • 35
  • 37
17

Function templates (like skypjack's example) and partial specializations of class and variable templates can have multiple parameter packs if each template parameter subsequent to a template parameter pack either has a default value or can be deduced. The only thing I'd like to add/point out is that for class and variable templates you need a partial specialization. (See: C++ Templates, The Complete Guide, Vandevoorde, Josuttis, Gregor 12.2.4, Second Edition)

// A template to hold a parameter pack
template < typename... >
struct Typelist {};

// Declaration of a template
template< typename TypeListOne 
        , typename TypeListTwo
        > 
struct SomeStruct;

// Specialization of template with multiple parameter packs
template< typename... TypesOne 
        , typename... TypesTwo
        >
struct SomeStruct< Typelist < TypesOne... >
                 , Typelist < TypesTwo... >
                 >
{
        // Can use TypesOne... and TypesTwo... how ever
        // you want here. For example:
        typedef std::tuple< TypesOne... > TupleTypeOne;
        typedef std::tuple< TypesTwo... > TupleTypeTwo;
};      
8

The compiler needs a way to know where is the barrier between the two variadic templates. A clean way of doing this is to define one pack of arguments for an object and the second pack for a static member function. This can be appied to more than two variadic templates by nesting multiple structs in eachother. (keeping the last level as a function)

#include <iostream>

template<typename... First>
struct Obj
{
    template<typename... Second>
    static void Func()
    {
        std::cout << sizeof...(First) << std::endl;
        std::cout << sizeof...(Second) << std::endl;
    }
};

int main()
{
    Obj<char, char>::Func<char, char, char, char>();
    return 0;
}
Nandee
  • 598
  • 6
  • 11
-2
two<>();
two<int>(a);
two<int>(a, b);

see https://www.youtube.com/watch?v=va9I2qivBOA 29:00-35:00

Estin
  • 1
  • 3
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/31263837) – mazecreator Mar 14 '22 at 14:27