在不同的模板参数包之间分发非类型参数包

Distribute non-type parameter pack across different template parameter packs

本文关键字:之间 类型参数 包之间 参数      更新时间:2023-10-16

有没有任何语法可以让我在模板参数包的参数之间分发非类型参数包,期望非类型包(不同大小(?由于这很令人困惑,我相信一个例子可能有助于澄清我的意思: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的行使用与我所使用的语法类似的语法。显然,如果我编写一个特殊的构造函数来处理这种情况,它会很好地工作。然而,我希望这适用于任何数量的容器,而不必为所有可能的情况明确说明。例如,如果我有两个容器a,b,其中包含索引集{0,1,2}{0,1,2,3},那么扩展应该看起来像A(a[0],a[1],a[2], b[0],b[1],b[2],b[3])

我知道我可以递归地完成这项工作,一次拆包一个容器,并递归地委托给一开始只期望一个平面元素序列的构造函数。我的问题是,以一种更优雅、高效、不那么冗长的方式,这是否可行。

例如,如果我有两个容器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
}

我只想做:

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"), ...);
}
}; 

演示

使用multi_apply(多元组的std::apply版本((您可以在那里找到(,您可以执行:

struct A
{
template<typename...  Ts, REQUIRES(!is_container_type<Ts>::value || ...)>
A(Ts... ts)
{
((std::cout << ts << " "), ...);
std::cout << std::endl;
}

template<typename...  Cs, REQUIRES(is_container_type<Cs>::value && ...)>
A(Cs... cs) :
A(multiple_apply([](auto...args){ return A(args...); },
cs...) // if Cs is tuple-like
// as_tuple(cs)...) // convert Cs to tuple<int, /*..*/, int>
)
{
}
}; 

演示

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

我尝试了一个不同的版本,其中没有构造新的对象,但由于某种原因(我认为这是由于多次扩展(,编译器未能推导出正确的构造函数——这个版本在上面链接的示例中的GROW_VECTOR 0定义中。