web-dev-qa-db-fra.com

Comment puis-je avoir plusieurs packs de paramètres dans un modèle variadic?

Fonction n () accepte un pack de paramètres. Fonction deux () accepte deux. Chaque pack est contraint d'être enveloppé dans des types A et B . Pourquoi est-il impossible d'instancier 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);
}

Essayé avec gcc et 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.
47
Samuel Danielson

Voici une autre façon d'avoir plusieurs packs de paramètres à l'aide de paramètres de modèle de modèle:

#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;
}
34
Alexandre Hamez

J'ai trouvé une solution. Enveloppez chaque pack de paramètres dans un tuple. Utilisez une structure pour une spécialisation partielle. Voici une démo qui transmet des arguments à un foncteur en consommant un Tuple en tant que liste et en en accumulant un autre. Eh bien, celui-ci avance en copiant. Les tuples sont utilisés dans la déduction de type mais aucun tuples n'est utilisé dans les paramètres de fonction, ce qui, je pense, est net.

#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);
}

Les tuples sont une très bonne liste de temps de compilation.

15
Samuel Danielson

Les modèles de fonction (comme l'exemple de skypjack) et spécialisations partielles des modèles de classe et de variable peuvent avoir plusieurs packs de paramètres si chaque paramètre de modèle suivant un pack de paramètres de modèle a une valeur par défaut ou peut être déduit. La seule chose que je voudrais ajouter/souligner, c'est que pour les modèles de classe et de variable, vous avez besoin d'une spécialisation partielle. (Voir: Modèles C++, 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;
};      
7
Mathematical Joe