将非类型参数包分布在不同的模板参数包中

时间:2019-10-24 14:03:55

标签: c++ templates c++17 variadic-templates index-sequence

是否有任何语法可用于在模板参数包的参数之间分配非类型参数包,从而期望非类型包(大小不同)?由于这很令人困惑,我相信可以举一个例子来阐明我的意思:https://godbolt.org/z/FaEGTV

template <typename T, int... I> struct Vec { };

struct A
{
    template<template<typename, int...> typename...  Container,
        typename... Ts, int... Is>
    A(Container<Ts,Is...>... );
};  

A a(Vec<int, 0>{}, Vec<double, 0>{});       // ok
A b(Vec<int, 0, 1>{}, Vec<double, 0, 1>{}); // ok
A c(Vec<int, 0>{}, Vec<double, 0, 1>{});    // error

我希望标记为// error的行使用与我相似的语法。显然,如果我编写一个特殊的构造函数来处理这种情况,它将很好地工作。但是,我希望此方法适用于任何数量的容器,而不必在所有可能的情况下都明确说明它。例如,如果我有2个容器a,b,分别具有索引集{0,1,2}{0,1,2,3},则扩展应类似于A(a[0],a[1],a[2], b[0],b[1],b[2],b[3])

我知道我可以递归地执行此操作,一次解压缩一个容器,然后递归地委托给构造函数,这些构造函数期望一开始只包含平面元素。我的问题是,以更优雅,更有效,更省力的方式这样做是否可行。

3 个答案:

答案 0 :(得分:1)

  

例如,如果我有2个容器a,b,具有索引集{0,1,2}{0,1,2,3},则扩展应该类似于A(a[0],a[1],a[2], b[0],b[1],b[2],b[3])

     

我知道我可以递归地执行此操作,一次解压缩一个容器,然后递归地委托给构造函数,这些构造函数期望一开始只包含平面元素。我的问题是,以更优雅,更有效,更省力的方式这样做是否可行。

您是否接受扩展为std::tuplea[0],a[1],a[2], b[0],b[1],b[2],b[3]的解决方案?

在这种情况下,您可以遵循Igor的建议,将容器中的值解包,然后将它们重新打包为元组,然后使用std::tuple_cat()来连接元组。

我的意思是...给出了如下的容器/元组转换器

template <template<typename, std::size_t...> typename C,
          typename T, std::size_t... Is>
auto getTpl (C<T, Is...> const & v)
 { return std::make_tuple(v.data[Is]...); } 

您可以开始编写构造函数,如下所示调用委托构造函数

   template <typename ... Ts>
   A (Ts const & ... ts) : A{ std::tuple_cat( getTpl(ts)... ) }
    { } 

最后的构造函数是

   template <typename ... Ts>
   A (std::tuple<Ts...> const & tpl)
    { /* do something with values inside tpl */ }

以下是完整的编译示例

#include <iostream>
#include <string>
#include <tuple>

template <typename T, std::size_t ... Is>
struct Vec
 {
   T data [sizeof...(Is)] = { Is... };

   T const & operator[] (std::size_t i) const
    { return data[i]; }

   T & operator[] (std::size_t i)
    { return data[i]; }
 };

template <template<typename, std::size_t...> typename C,
          typename T, std::size_t... Is>
auto getTpl (C<T, Is...> const & v)
 { return std::make_tuple(v.data[Is]...); }

struct A
 {
   template <typename ... Ts>
   A (std::tuple<Ts...> const & tpl)
    { /* do something with values inside tpl */ }

   template <typename ... Ts>
   A (Ts const & ... ts) : A{ std::tuple_cat( getTpl(ts)... ) }
    { } 
 };  

int main ()
 {
   A a(Vec<int, 0>{}, Vec<double, 0>{});       // ok
   A b(Vec<int, 0, 1>{}, Vec<double, 0, 1>{}); // ok
   A c(Vec<int, 0>{}, Vec<double, 0, 1>{});    // ok, now
 }

答案 1 :(得分:1)

我会简单地做:

IntelliJ IDEA 2019.2.3

Demo

使用template <typename T> struct is_container_type : std::false_type{}; template<template<typename, size_t...> typename C, typename T, size_t... Is> struct is_container_type<C<T, Is...>> : std::true_type { // Potential `using` to retrieve C, T, Is... }; struct A { template<typename... Cs, REQUIRES(is_container_type<Cs>::value && ...)> A(Cs... cs) { ((void)(std::cout << cs << "\n"), ...); } }; (多个元组的std::apply版本)(您可以找到there),您可以这样做:

multi_apply

Demo

答案 2 :(得分:0)

我设法产生了一个效率不高的解决方案,该方案在每个递归步骤的第一个参数中都构建了一个更大的向量,然后还有一个接受单个向量并将其扩展的构造函数。显然,这是不理想的,因为在最坏的情况下,会创建N个向量并包含元素N^2/2的总数。这是一个示例实现,说明我做了什么:https://coliru.stacked-crooked.com/a/1f41f3793846cdb1

我尝试了一个不同的版本,其中没有构造新对象,但是由于某种原因,编译器未能设法推断出正确的构造函数(我认为这是由于多次扩展引起的)-此版本位于{{1 }}在上面链接的示例中进行定义。