【问题标题】:Distribute non-type parameter pack across different template parameter packs跨不同模板参数包分发非类型参数包
【发布时间】:2019-10-24 14:03:55
【问题描述】:

是否有任何语法可以通过它在模板参数包的参数中分发非类型参数包,期望非类型包(不同大小)?由于这很令人困惑,我相信一个例子可能有助于澄清我的意思: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])

我知道我可以递归地执行此操作,一次拆包一个容器,然后递归地委托给构造函数,期望在一开始只有一系列扁平元素。我的问题是,这是否以更优雅、更高效、更简洁的方式可行。

【问题讨论】:

  • 嗯。在这个例子中,我什至对 b 编译感到惊讶。
  • @Barry 因为通常不同大小的扩展不能一起工作?我认为是因为它仅限于Container&lt;Ts,Is...&gt;
  • Container&lt;Ts,Is...&gt;... 说:对于Ts 中的每个T,使用ContainerT 以及所有Is。这就是为什么所有Is 必须相同的原因。编译示例的最简单方法是 template &lt;typename... Cs&gt; A(Cs...); 然后您可以使用帮助模板从 Cs 中的每个 C 中提取 TIs
  • @IgorTandetnik 我知道Is... 在做什么。我希望我可以很好地扩展它。如前所述,我的另一个选择是使用递归,我现在正在尝试使用递归,但没有取得多大成功。
  • 我认为伊戈尔的回答是最好的方式。根据您想要实现的确切目标,您根本不必使用递归。

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


【解决方案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
 }

【讨论】:

  • 这似乎与我在暂定答案中所做的完全一样,但是使用的是增长的元组而不是向量并且没有递归(显然,您的方法可以扩展为像我一样从标量生成元组做过)。该连接元组是否会优化为一般的平面扩展(例如使用-O2)?因为否则它会在最坏的情况下创建 N 个元组,总共需要 N^2/2 个元素。
  • @lightxbulb - 是的,我的回答是避免递归。关于优化...坦率地说我不知道​​。
【解决方案2】:

我会这样做:

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

Demo

使用multi_apply(多个元组的std::apply版本)(你可以找到there),你可以这样做:

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>
          )
    {
    }

}; 

Demo

【讨论】:

  • 在我最初的问题中(在 Barry 编辑之前),我曾提到重点不在于使用 std::cout 并因此使用 &lt;&lt;。因为很明显,人们可以为容器定义运算符,然后像你一样调用它(这是我最初的示例实际所做的,在它被编辑之前)。关键是将平面扩展传递给任何函数(不一定是可能重载的运算符)。更具体地说,在这种情况下,是一个构造函数。最初的问题是尝试为给定向量生成所有可能的构造函数,所以如果你有一个 vec4,你会......
  • ... 演员:vec4(vec3, T)vec4(T, vec3)vec4(vec2,vec2)vec4(vec2,T,T)vec4(T,vec2,T)vec4(T,T,vec2)vec4(T,T,T,T)。关键是让编译器自动生成它们,并在生成完全相同的代码(在性能方面)的同时,就像我要手动编写它们一样(如果使用合适的优化标志编译,例如 -O2)。一件事导致另一件事,我意识到 C++ 不能像我希望的那样进行模式匹配(例如 Prolog)。
  • 我把这个问题理解为巴里......但事实上,你最想要一个用于多个元组的std::apply
  • 是的,我会这么说——对于任何元组组合。以为我不确定是否可以从std::apply 调用构造函数。无论如何,max66 的答案可能是我能得到的最接近的答案,虽然我不认为编译器会优化这个,所以很可能我仍然需要手工编写,或者编写代码生成器。
  • 就我的意思而言,C++ 不能像我想象的那样进行模式匹配,请参阅:coliru.stacked-crooked.com/a/1f41f3793846cdb1GROW_VECTOR 0。是我搞砸了什么,还是 C++ 编译器不可能真正匹配那些构造函数?
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-18
    • 1970-01-01
    • 1970-01-01
    • 2011-08-06
    相关资源
    最近更新 更多