带有空参数包的递归可变参数模板(以避免重复基本情况)

时间:2017-04-07 12:04:10

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

我正在尝试使用C ++递归模板,我不知道为什么我的模板无效。

假设我想定义一个递归函数,该函数接受可变数量的参数(针对不同类型)。

我已经查看了很多可变参数模板的示例,到目前为止我所看到的所有内容都使用单独的模板专门化来指定基本情况。

但是,我认为使用单个模板会更好(在某些情况下至少是这样),它定义了基本案例以及递归案例。

我认为如果你在函数中有很多通用逻辑,这种方法特别好,你必须为你的基本案例实例复制(在两个不同的地方完全相同的代码)。

以下示例中的第二个模板应该是我的解决方案。我认为这个模板应该在它自己的功能上运行。但事实并非如此。

没有第一个模板,代码无法编译:

error: no matching function for call to
      'add_elems'
        return head[i] + add_elems(i, second, tail...);
                         ^~~~~~~~~
in instantiation of function
      template specialization 'add_elems<double, std::__1::vector<double, std::__1::allocator<double> >>' requested here

...

tail只包含一个参数时,模板显然会制动。但是,add_elems(i, second, tail...)不应该对模板有效 template<typename V, typename S, typename... T> V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)tail

我不知道这是否依赖于编译器,但我使用的是clang。

#include <iostream>
#include <vector>

/* This template is the exact same as the below template with an
      empty parameter pack as tail. I want my code to be working 
      without this specialisation */
template<typename V, typename S>
V add_elems(size_t i, const std::vector<V>& head, const S& second)
{
    /* Imagine some more code here */
    return head[i] + second[i];
}

template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
    /* Imagine some more code here (the same as above) */

    if (sizeof...(tail) > 0)
        return head[i] + add_elems(i, second, tail...);
    else
        return head[i] + second[i];
}

int main()
{
    std::vector<double> a({1, -3, -3});
    std::vector<double> b({2, -2, 1});
    std::vector<double> c({4, -4, -11});
    std::vector<double> d({4, 10, 0});

    std::cout << "Result: " << add_elems(0, a, b, c, d);
    std::cout << " ," << add_elems(1, a, b, c, d);
    std::cout << " ," << add_elems(2, a, b, c, d);
}

5 个答案:

答案 0 :(得分:4)

问题是您的if声明不是constexpr。这意味着所有代码路径都需要为add_elems

的每个潜在调用进行编译

这意味着最终你最终会遇到tail只是一个元素的情况,并且编译器需要评估add_elems(size_t&, const, std::vector<double>&),因为没有second参数而不存在template<typename V, typename S, typename... T> V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail) { if constexpr (sizeof...(tail) > 0) return head[i] + add_elems(i, second, tail...); else return head[i] + second[i]; }

如果你能够有一个constexpr if语句,那么这一切都可以很好地工作,因为编译器甚至不会编译坏分支计算结果为false,因此不会查找不存在的函数:

-std=c++1z

Demo(需要 Clang 3.9.1 或更高版本以及template<typename... T> decltype(auto) add_elems(size_t i, const T&... elems) { return (elems[i] + ...); } 选项。)

如果您有权访问C ++ 17,那么您可以使用unary right fold实现此目的:

-std=c++1z

Demo 2(需要 Clang 3.6.0 或更高版本以及duration = row[2] - row[1]选项。)

答案 1 :(得分:2)

等待C ++ 17,我提出了一个C ++ 11不太好的解决方案,跟随AndyG一个

template <typename T0, typename ... T>
auto add_elems2 (size_t i, T0 const & elem0, T const & ... elems)
   -> decltype(elem0[i])
 {
   using unused=int[];

   auto ret = elem0[i];

   unused a { (ret += elems[i], 0)... };

   return ret;
 }

答案 2 :(得分:1)

正如错误消息所示,您目前遇到的问题是,如果tail为空,则调用add_elems(i, second, tail...)与函数的定义不匹配。即使if语句中的布尔表达式是constexpr,直到c ++ 1z,函数的整个主体必须​​是有效的。

@AndyG提供了一种方法,c ++ 1z可以处理这个问题,另一种方法是if constexpr,它允许编译时分支&#34;。其中任何一个都允许您对模板进行一次(主要)专业化。

// Only in c++1z
template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
    /* Imagine some more code here (the same as above) */

    if constexpr (sizeof...(tail) > 0)
        return head[i] + add_elems(i, second, tail...); // this is not required to be valid when the if is false
    else
        return head[i] + second[i]; // this is not required to be valid when the if is true (but it is happens to be valid anyway)
}

答案 3 :(得分:1)

正如许多人所指出的,这在C ++ 1z中很容易。它可以在C ++ 14中完成,这很难。

template<class True, class False>
True pick( std::true_type, True t, False ) {
  return std::move(t);
}
template<class True, class False>
False pick( std::false_type, True, False f ) {
  return std::move(f);
}
template<bool b>
constexpr std::integral_constant<bool, b> bool_k;


template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
  return
    pick( bool_k<(sizeof...(tail)>0)>,
      [&](const auto&... tail)->V{
        // tail... template argument hides function argument above:
        return head[i] + add_elems(i, second, tail...);
      },
      [&]()->V{
        return head[i] + second[i];
      }
    )
    ( tail... );
};

我们使用pick对两个lambdas中的一个进行编译时调度。

这些lambdas占用了auto参数不同的代码,这使得它们成为模板。只要它们对某些auto参数(即使它们“从未被调用过”)有效,它们就是合法的C ++。

我们已经完成的工作是隐藏lambdas中的两个重载。由于C ++ 11没有模板lambdas,这种“隐藏重载”技术在C ++ 11中不起作用。

答案 4 :(得分:1)

您可以使用Boost.Hana来模拟C ++ 14中about.php的行为。例如:

if constexpr