如何在可变参数模板中有多个参数包?

Posted

技术标签:

【中文标题】如何在可变参数模板中有多个参数包?【英文标题】:How can I have multiple parameter packs in a variadic template? 【发布时间】:2012-04-07 13:24:23 【问题描述】:

函数one() 接受一个参数包。函数 two() 接受两个。每个包都被限制在 AB 类型中。为什么无法实例化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);

尝试使用 gcc 和 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.

【问题讨论】:

A&lt;Ts&gt; ...as -- 这作为参数是否合法? 编译器如何知道一个包何时结束而另一个包何时开始? @ildjarn:因为一个包必须有类型包装在 A 中,而另一个包必须有类型包装在 B 中。 委员会考虑(或至少这样做,不确定当前状态)更改有关此的规则,因此当前编译器实现在处理它们方面有所不同。例如,clang 拒绝对带有参数 > 1 的 template&lt;class ...A,class...B&gt;void f(A...a,B...b) 的调用(因为“A...a”是非推导上下文,因为它不是最后一个函数参数包,也不能是“catch-all”-推导出为一个空包,因为它不是 f 的尾随模板参数包,但 GCC 接受它,给 A 空包列表和 B 所有传入参数类型。 在 C++14/17 中是否对此有进一步的说明? 【参考方案1】:

这是另一种使用模板模板参数拥有多个参数包的方法:

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

【讨论】:

这种方法也可以应用于类模板吗? @DrewNoakes 我不这么认为,因为我使用函数的参数来打包模板参数。【参考方案2】:

我找到了一种解决方案。将每个参数包包装在一个元组中。使用结构进行部分特化。这是一个演示,它通过将一个元组作为列表使用并累积另一个来将参数转发给函子。好吧,这个通过复制转发。类型推导使用元组,函数参数中没有使用元组,我认为这很整洁。

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

元组是一个非常好的编译时间列表。

【讨论】:

【参考方案3】:

函数模板(如skypjack的例子)和类和变量模板的部分特化可以有多个参数包,如果模板参数包之后的每个模板参数要么有一个默认值,要么可以推导出来。我想添加/指出的唯一一件事是,对于类和变量模板,您需要部分专业化。 (参见:C++ 模板,完整指南,Vandevoorde,Josuttis,Gregor 12.2.4,第二版)

// 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;
;      

【讨论】:

@mathematical_Joe 您或其他人能否提供一个有关如何实例化 SomeStruct 对象的示例? @UnSat SomeStruct&lt;std::tuple&lt;int, double&gt;, std::tuple&lt;bool, const char*, float&gt;&gt;; struct TypeList 可以省略,因为 std::tuple 可以很好地处理它。 这里可以应用CTAD来避免提及完整类型吗?【参考方案4】:

编译器需要一种方法来知道两个可变参数模板之间的障碍在哪里。一种干净的方法是为对象定义一组参数,为静态成员函数定义第二组参数。这可以通过在彼此中嵌套多个结构来应用于两个以上的可变参数模板。 (将最后一级保留为函数)

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

【讨论】:

以上是关于如何在可变参数模板中有多个参数包?的主要内容,如果未能解决你的问题,请参考以下文章

第20课 可变参数模板_模板参数包和函数参数包

如何对可变参数模板函数的异构参数包进行通用计算?

第21课 可变参数模板_展开参数包

C++ Primer 5th笔记(chap 16 模板和泛型编程)可变参数模板

如何通过可变参数模板将多个构造函数参数转发到数组初始值设定项列表?

如何按索引从可变参数模板参数包中提取值?