【问题标题】:Multiple parameter pack workarounds多个参数包解决方法
【发布时间】:2021-12-27 07:43:13
【问题描述】:

有时您只是发现自己想在 C++ 中使用多个参数包。

考虑以下伪代码:

template<class...T1, int, class...T2>
struct bar
{
  tuple<vector<T1>...> v1;
  tuple<vector<T2>...> v2;
};

int main()
{
  bar<int, bool, char, 0, long int, char> my_bar;
  return 0;
}

理论上,某种语言的编译器可以编译此代码,因为int 作为两个参数包之间的分隔符可以让编译器成功解析模板参数列表并识别两个唯一的参数包。然而,这在 C++ 中不受支持,考虑到做这种事情的技术根本不存在,这是有道理的。

考虑以下伪代码:

template<class...T1, class...T2>
struct bar
{
  tuple<vector<T1>...> v1;
  tuple<vector<T2>...> v2;
  constexpr bar(const tuple<T1...>, const tuple<T2...>) { };
};

int main()
{
  bar my_bar(tuple<int, bool, char>(), tuple<long int, char>());
  return 0;
}

理论上,某种语言的编译器可以编译此代码,因为可以根据传递给构造函数的参数自动推导出参数包。如果对象的构造方式使得无法推断模板参数,则可能会引发编译器错误。然而,这在 C++ 中不受支持,考虑到做这种事情的技术根本不存在,这是有道理的。

这是一种解决方法。考虑以下有效的 C++ 代码:

template<class...T1>
struct bar
{
  template<class...T2>
  struct dummy
  {
    tuple<vector<T1>...> v1;
    tuple<vector<T2>...> v2;
  };
};

int main()
{
  bar<int, bool, char>::dummy<long int, char> my_bar;
  return 0;
}

这是一种分离参数包的方法,但它并不优雅。谁想在他们的代码中间写“虚拟”?

除了您知道的多个参数包之外,还有其他替代方法吗?

【问题讨论】:

  • 那么,问题是什么?
  • 我建议将您的问题重新表述为关于您解决的问题的问题,然后将您的解决方案移至答案。自我回答的问题在这里非常好——它们对未来的访问者很有用,并允许其他人提供替代解决方案。如果您有两个不能很好地组合在一起的解决方案,您甚至可以考虑将它们分成两个单独的答案,以便分别对每个答案进行投票和评论。

标签: c++ templates tuples variadic-templates template-meta-programming


【解决方案1】:

即使没有变通方法和辅助类,您也可以从构造函数参数推断类型。

您需要两个额外的步骤:

  1. 为您的类定义使用部分特化
  2. 使用用户定义的推导指南从构造函数参数中获取模板参数的类型。
// first define the template, we will have 2 parameters for it
template < typename PACK_ONE, typename PACK_TWO >
class bar;

// Specialize it for two tuples with a different parameter pack for each
template< class ... T1, class...T2 >
struct bar< std::tuple< T1...>, std::tuple< T2...>>
{
    std::tuple<std::vector<T1>...> v1; 
    std::tuple<std::vector<T2>...> v2; 
    constexpr bar(const std::tuple<T1...>, const std::tuple<T2...>) { }
};

// use a user defined deduction guide to deduce the type of the class from the constructor parameters
template< class ... T1, class...T2 >
bar( const std::tuple<T1...>, const std::tuple<T2...>) -> bar< std::tuple<T1...>, std::tuple<T2...> >;

int main()
{
    // and simply use the class by giving the parameters as wanted
    bar my_bar(std::tuple<int, bool, char>{}, std::tuple<long int, char>{});
    return 0;
}

提示:您应该使用 const ref 参数,因为您的副本较少。我将示例尽可能靠近您给定的问题。

【讨论】:

    【解决方案2】:

    我想出了另一种选择。考虑以下实用程序代码:

    namespace util
    {
      namespace
      {
        template<template<class...> class T1, template<class...> class T2, class...T3>
        T1<T2<T3>...> foo(std::tuple<T3...>);
      }
    
      template<template<class...> class T1, template<class...> class T2, class T3>
      using composition = decltype(foo<T1, T2>(T3()));
    };
    

    它的工作方式是使用一个永远不会被调用的私有虚拟函数foo。该函数的全部目的是自动推断元组类型内的基础参数包。使用这个参数包的类型计算写在 foo 的返回类型中。然后我们有composition,当 foo 被传递一个其类型被假定为 std::tuple 的特化的参数时,它推导出 foo 的返回类型。我们还向 foo 传递了一些类型计算所需的模板参数,但省略了需要自动推导的参数。

    composition 的用法:设 T1 为可变参数模板。令 T2 为单参数模板。让 T3 成为 std::tuple 的一个特化,它只用作参数包的包装器。结果是util::composition&lt;T1, T2, std::tuple&lt;int, char, bool&gt;&gt;T1&lt;T2&lt;int&gt;, T2&lt;char&gt;, T2&lt;bool&gt;&gt; 相同。本质上,composition 从一堆 T2 中“组成”了一个 T1,每个 T2 都由 T3 中的一个类型组成。

    这让我可以做一些很酷的事情。考虑以下代码:

    template<class T1, class T2>
    struct bar
    {
      util::composition<tuple, vector, T1> v1;
      util::composition<tuple, vector, T2> v2;
    };
    
    int main()
    {
      bar<tuple<int, bool, char>, tuple<long int, char>> my_bar;
      return 0;
    }
    

    在主函数中,我通过将每个参数包嵌套在一个元组中来分隔参数包。然后在我的类模板bar 中,我使用composition 创建向量元组。


    如果您想测试,这里是完整的代码。

    #include <tuple>
    #include <vector>
    #include <iostream>
    using namespace std;
    
    namespace util
    {
      namespace
      {
        template<template<class...> class T1, template<class...> class T2, class...T3>
        T1<T2<T3>...> foo(std::tuple<T3...>);
      }
    
      template<template<class...> class T1, template<class...> class T2, class T3>
      using composition = decltype(foo<T1, T2>(T3()));
    };
    
    template<class T1, class T2>
    struct bar
    {
      util::composition<tuple, vector, T1> v1;
      util::composition<tuple, vector, T2> v2;
    };
    
    int main()
    {
      bar<tuple<int, bool, char>, tuple<long int, char>> my_bar;
    
      cout << std::is_same<decltype(my_bar.v1), tuple<vector<int>, vector<bool>, vector<char>>>::value << endl;
      cout << std::is_same<decltype(my_bar.v2), tuple<vector<long int>, vector<char>>>::value << endl;
    
      return 0;
    }
    

    预期输出:

    1
    1
    

    【讨论】:

      【解决方案3】:

      只需将std::tuple(或自定义类型列表)作为分组,并使用部分特化:

      template <class Pack1, class Pack2> struct bar;
      
      template <class... T1, class... T2>
      struct bar<std::tuple<T1...>, std::tuple<T2...>>
      {
        std::tuple<std::vector<T1>...> v1;
        std::tuple<std::vector<T2>...> v2;
      };
      

      用法类似于

      bar<std::tuple<int, bool, char>, std::tuple<long int, char>> my_bar;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-02-21
        • 1970-01-01
        • 1970-01-01
        • 2017-06-16
        • 2021-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多