【问题标题】:How do I write a variadic template function in C++ where the parameter pack is not the last parameter?如何在参数包不是最后一个参数的 C++ 中编写可变参数模板函数?
【发布时间】:2018-05-31 05:26:30
【问题描述】:

我正在尝试使用参数包不是列表中的最后一个参数的可变参数模板函数。请注意,有两个递归调用 - 一个在包前面删除一个参数,另一个调用在包之后删除一个参数。

  • 我的编译器似乎是:Apple LLVM 版本 8.1.0 (clang-802.0.42)
  • 如果我能让它工作,下面所有的 int 都将是一个新的 T 模板参数。

如果 Blender 的调用站点不能干净,那么使用 ... 毫无意义。在这种情况下,我可以自己扩展 Blender 的几个重载。我真的不想诉诸于此。我希望我只是错过了一些东西。

int Blender( double t, int i)
{
    return i;
}

template <typename ...Args>
  int Blender( double t, int first, Args... more, int last)
{
    return (1-t)*Blender(t, first, more...) + t*Blender(t, more..., last);
}

static void tryit()
{
    Blender(.5, 23, 42, 89); //doesn't compile
}

【问题讨论】:

  • 长话短说,这是不可能的。包在设计上是“贪婪”的。
  • 可能能够做到这一点,就像在 C++ 中进行柯里化一样。扭曲是拉出最后一个参数并做任何你想做的特殊事情。 stackoverflow.com/questions/152005/…
  • 这些数字是从哪里来的?只有在使用编译时常量时才有可能。
  • @Jodocus,实际上它们根本不是数字。它们是可以添加和乘以双精度(线性组合)的对象。并且这些值在编译时是未知的。
  • 如果你打开它,你的 xcode 版本支持一些c++14 功能。如果你能做到,那将有所帮助;你能做到吗?

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


【解决方案1】:

我提出了另一种解决方案,与问题中的代码完全不同,我认为效率更高(仅在一个 std::array 上创建;通过模板索引序列获得的搅拌器)

#include <array>
#include <utility>
#include <iostream>

template <typename T, std::size_t I0>
int blenderH (double t, T const & arr, std::index_sequence<I0> const &)
 { return arr[I0]; }

template <typename T, std::size_t I0, std::size_t ... Is>
auto blenderH (double t, T const & arr, 
               std::index_sequence<I0, Is...> const &)
   -> std::enable_if_t<0U != sizeof...(Is), int>
 { return   (1-t) * blenderH(t, arr, std::index_sequence<(Is-1U)...>{})
          +    t  * blenderH(t, arr, std::index_sequence<Is...>{}); }

template <typename ... Args>
int blender (double t, Args ... as)
 {
   static constexpr auto size = sizeof...(Args);

   return blenderH(t, std::array<int, size>{ { as... } },
                   std::make_index_sequence<size>{});
 }

int main()
 { std::cout << blender(.3, 23, 42, 89) << std::endl; }

不幸的是,这个解决方案从 C++14 开始也可以工作(std::index_sequencestd::make_index_sequence)。

-- 编辑--

卡莱斯说。

对这里发生的事情进行一些解释会有所帮助。我对第二次重载的正文中缺少I0 感到困惑。

我试着解释一下。

假设被称为BlenderH()(第二个重载)的递归版本,在std::index_sequence 值中有一个索引列表。说5、6、7和8;所以I05Is...6, 7, 8

我们必须递归调用blenderH(),首先使用索引5, 6, 7,然后使用6, 7, 8

我们可以避免使用I0 (5) 因为

  • 5, 6, 7 是从 6, 7, 8 每个值减 1 获得的(所以第一次递归调用的 std::index_sequence&lt;(Is-1U)...&gt;

  • 6, 7, 8Is... 没有修改(所以std::index_sequence&lt;Is...&gt; 在第二个。

从实际的角度来看,I0 被声明为只被丢弃;没有必要使用它。

【讨论】:

  • 对这里发生的事情进行一些解释会有所帮助。我对第二次重载的正文中缺少I0 感到困惑。
  • @Caleth - 我不擅长解释,但是...修改了答案。
【解决方案2】:
template<std::size_t I>
using count = std::integral_constant<std::size_t, I>;

namespace details {

  template<class...Args, std::size_t N,
    typename std::enable_if<sizeof...(Args)==N, bool>::type = true
  >
  int Blender( count<N> drop, double t, int i, Args...args ) {
    return i;
  }
  template<class...Args, std::size_t N,
    typename std::enable_if<sizeof...(Args)!=N, bool>::type = true
  >
  int Blender( count<N> drop, double t, int i, Args...args ) {
    return (1-t)*Blender( count<N+1>{}, t, i, args... ) + t*Blender( count<N>{}, t, args... );
  }
}

template <typename ...Args>
int Blender( double t, int first, Args... more)
{
  return details::Blender( count<0>{}, first, more... );
}


static void tryit()
{
  Blender(.5, 23, 42, 89); //doesn't compile
}

这里count&lt;N&gt; 计算最后要忽略的参数数量。

两个details 重载涵盖了N 等于包中的参数数量(因此我们还剩下1 个参数)和不等于的情况。他们被派往使用 SFINAE。

【讨论】:

    【解决方案3】:

    一旦你意识到限制在于类型包推导而不是函数调用,这实际上真的很容易。我们只需要一种方法来使用我们可以推断的类型包并显式传递它,避免在需要提取最后一个参数的函数处进行推断:

    int Blender( double t, int i)
    {
        return i;
    }
    
    template <typename ...Args>
      int Blender( double t, int first, Args... more);
    
    template <typename ...Args>
      int BlenderWithoutLast( double t, Args... more, int last)
    {
        return Blender(t, more...);
    }
    
    template <typename ...Args>
      int Blender( double t, int first, Args... more)
    {
        return (1-t)*BlenderWithoutLast<Args...>(t, first, more...) + t*Blender(t, more...);
        //    all the magic happens here ^^^^^
    }
    

    现在您的测试用例编译并运行

    #include <iostream>
    int main()
    {
        std::cout << Blender(.5, 23, 42, 89);
    }
    

    对我来说,这适用于 clang 和 --std=c++11

    【讨论】:

    • 太棒了! Ubi maior 未成年人停止。
    • @max66: 试图弄清楚如何承认你首先考虑了辅助函数,因为我不会找到让它变得如此简单的传递模板类型实际参数没有你的暗示帮助函数的结构来做到这一点。
    猜你喜欢
    • 2013-01-24
    • 2012-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多