【问题标题】:Tuple to parameter pack元组到参数包
【发布时间】:2016-08-05 09:17:27
【问题描述】:

来自用户 Faheem Mitha 的以下代码基于用户 Johannes Schaub - litb 在此 SO 中的回答。这段代码完美地完成了我所寻求的,即将tuple 转换为参数包,但我对这段代码的理解不够好,因此我想我会创建一个新的讨论,可能有助于像我这样的模板元编程新手。所以,请原谅重复的帖子。

现在进入代码

#include <tuple>
#include <iostream>
using std::cout;
using std::endl;

template<int ...> struct seq {};

template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> { };

template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

double foo(int x, float y, double z)
{
    return x + y + z;
}

template <typename ...Args>
struct save_it_for_later
{
    std::tuple<Args...> params;
    double(*func)(Args...);

    double delayed_dispatch()
    {
        return callFunc(typename gens<sizeof...(Args)>::type()); // Item #1
    }

    template<int ...S>
    double callFunc(seq<S...>)
    {
        return func(std::get<S>(params) ...);
    }
};

int main(void)
{
    std::tuple<int, float, double> t = std::make_tuple(1, (float)1.2, 5);
    save_it_for_later<int, float, double> saved = { t, foo };
    cout << saved.delayed_dispatch() << endl;
    return 0;
}

我完全被上面的第 1 项弄糊涂了:

  • typename 在那条线上有什么用途?
  • 我知道gens&lt;sizeof...(Args)&gt;::type() 将扩展为gens&lt;3&gt;::type(),但这似乎与template&lt;int N, int ...S&gt; struct gens : gens&lt;N - 1, N - 1, S...&gt; { };template&lt;int ...S&gt; struct gens&lt;0, S...&gt; 都不匹配。我显然没有抓住重点,如果有人能解释这里发生的事情,我会很高兴。

我知道callFunccallFunc(seq&lt;0,1,2&gt;) 这种形式调用,并且此方法本身的返回语句扩展为return func(std::get&lt;0&gt;(params), std::get&lt;1&gt;(params), std::get&lt;2&gt;(params),这就是使该方案有效的原因,但我无法确定seq&lt;0,1,2&gt; 类型如何已生成。

注意:不能使用std::index_sequence_for,我的编译器不支持 C++14 功能。

PS:这种技术可以归类为模板元编程吗?

【问题讨论】:

  • 1) 通知编译器gens&lt;sizeof...(Args)&gt;::type 是一种类型,而不是成员函数。 2) 参数包可以为空。
  • gens&lt;3&gt; 确实与 template&lt;int N, int ...S&gt; struct gens 匹配,N==3S 是一个空包。
  • 基本上,seqstd::index_sequencegensstd::make_index_sequence
  • @T.C.为什么编译器需要那个提示?我的意思是,除了seq&lt;S...&gt;() 构造函数调用之外,它怎么可能混淆gens&lt;sizeof...(Args)&gt;::type() 表达式?
  • 当你可以使用 C++17 时,它应该从 &lt;tuple&gt; 折叠到 std::apply()

标签: c++ templates c++11 variadic-templates


【解决方案1】:

让我们看看这里发生了什么:

template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> { };

template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

第一个是通用模板,第二个是特化,当第一个模板参数为 0 时应用。

现在,拿一张纸和铅笔,写下方法

 gens<3>

由上述模板定义。如果你的答案是:

 struct gens<3> : public gens<2, 2>

那么你是对的。这就是当N 为“3”且...S 为空时第一个模板的扩展方式。因此,gens&lt;N - 1, N - 1, S...&gt; 变为 gens&lt;2, 2&gt;

现在,让我们继续,看看gens&lt;2, 2&gt; 是如何定义的:

 struct gens<2, 2> : public gens<1, 1, 2>

这里,在模板扩展中,N 是 2,...S 是“2”。现在,让我们进行下一步,看看gens&lt;1, 1, 2&gt;是如何定义的:

 struct gens<1, 1, 2> : public gens<0, 0, 1, 2>

好的,现在gens&lt;0, 0, 1, 2&gt; 是如何定义的?现在可以通过专业化定义:

 template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

那么,struct gens&lt;0, 0, 1, 2&gt; 在这里会发生什么?好吧,在专业化中,“S...”变成了“0, 1, 2”,所以从某种意义上说,这变成了:

 struct gens<0, 0, 1, 2> {

   typedef seq<0, 1, 2> type;

 }

现在,请记住,所有这些都公开相互继承,“大象风格”,所以:

 gens<3>::type

最终成为一个 typedef 声明

 struct seq<0, 1, 2>

下面的代码使用另一个模板将元组转换为参数包:

double delayed_dispatch()
{
    return callFunc(typename gens<sizeof...(Args)>::type()); // Item #1
}

...Args 是元组参数。所以,如果元组中有三个元素,sizeof(...Args) 是 3,正如我上面解释的那样,gens&lt;sizeof...(Args)&gt;::type() 变为 gens&lt;3&gt;::type(),也就是 seq&lt;0, 1, 2&gt;()

那么,现在:

template<int ...S>
double callFunc(seq<S...>)
{
    return func(std::get<S>(params) ...);
}

S... 部分变为“0, 1, 2”,所以

std::get<S>(params)...

成为扩展为的参数包:

std::get<0>(params), std::get<1>(params), std::get<2>(params),

这就是元组如何成为参数包的方式。

【讨论】:

    【解决方案2】:

    在 C++17 中,您可以使用“if constexpr”来创建序列包装器:

    template <int indxMax, template <int... > class spack, int ... seq>
    constexpr auto get_seq17()
    {
        static_assert(indxMax >= 0, "Sequence size must be equal to or greater than 0!");
        if constexpr (indxMax > 0)
        {
            typedef decltype(spack<indxMax, seq...>{}) frst;
            constexpr int next = indxMax - 1;
            return get_seq17<next, spack, indxMax, seq...>();
        }
        else
        {
            return spack<indxMax, seq...>{};
        }
    }
    
    template <int indxMax, template <int...> class pack>
    struct seq_pack
    {
        typedef decltype(get_seq17<indxMax, pack>()) seq;
    };
    
    
    //creating a sequence wrapper
    template <int ... seq>
    struct seqpack {};
    
    //usage
    seq_pack<4, seqpack>::seq; //seqpack<0, 1, 2, 3, 4> 
    

    虽然这个实现更容易理解,但最好使用std::make_index_sequence&lt;Size&gt;,因为Julius 在下面的cmets 中已经提到。

    【讨论】:

    • 技术上你可以这样做,是的。但是,从 C++14 开始,您应该只使用 std::index_sequence 和朋友。 std::make_index_sequence&lt;N&gt; 可以使用 O(log(N)) 实例化来实现。此外,它可以被优化以从记忆中受益。特殊的内置编译器支持还可以允许 std 工具的 O(1)。您只能通过使用 O(N) constexpr if 解决方案使情况变得更糟。无论如何,为了理解这个概念(并带有适当的警告),您的答案很有用。 (而且它使用int,这很棒。)
    猜你喜欢
    • 2020-06-02
    • 2013-09-30
    • 1970-01-01
    • 2023-02-26
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 2017-02-23
    相关资源
    最近更新 更多