简体   繁体   English

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

[英]Recursive variadic template with empty parameter pack (to avoid duplication for base case)

I am experimenting with C++ recursive templates and I do not know why my template is not working. 我正在试验C ++递归模板,我不知道为什么我的模板不起作用。

Say I want to define a recursive function that takes a variable number of arguments (for different types). 假设我想定义一个递归函数,它接受可变数量的参数(对于不同的类型)。

I've have looked at many examples of variadic templates, and all that I've seen so far use a separate template specialisation to specify the base case. 我已经查看了很多可变参数模板的例子,到目前为止我所看到的所有内容都使用单独的模板特化来指定基本情况。

However, I think it would be nicer (in some cases at least) to use a single template, that defines the base case as well as the recursive cases. 但是,我认为使用单个模板会更好(在某些情况下至少是这样),它定义了基本案例以及递归案例。

I think this approach is especially nice if you have a lot of common logic in the function, which you would have to duplicate for your base case instance (exact same code in two different places). 我认为如果你在函数中有很多通用逻辑,这种方法特别好,你必须为你的基本案例实例复制(在两个不同的地方完全相同的代码)。

The second template in the example below is supposed to be my solution. 以下示例中的第二个模板应该是我的解决方案。 I would think that this template should be functioning on it's own. 我认为这个模板应该在它自己的功能上运行。 However this is not the case. 然而,这种情况并非如此。

Without the first template, the code does not compile: 没有第一个模板,代码不会编译:

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

...

Apparently the template braks when tail consists of just one parameter. 显然,当tail包含一个参数时,模板会进行制动。 But shouldn't add_elems(i, second, tail...) then still be valid for the template template<typename V, typename S, typename... T> V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail) with an empty 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)有效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

I do not know if this is compiler dependent, but I am using clang. 我不知道这是否依赖于编译器,但我使用的是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);
}

The problem is that your if statement is not constexpr . 问题是你的if语句不是constexpr Meaning that all code paths need to be compilable for every potential call to add_elems 这意味着每个潜在的add_elems调用都需要编译所有代码路径

This means that eventually you end up at a case where tail is just one element, and the compiler needs to evaluate add_elems(size_t&, const, std::vector<double>&) , which doesn't exist because there's no second argument. 这意味着最终你最终会遇到tail只是一个元素的情况,并且编译器需要评估add_elems(size_t&, const, std::vector<double>&) ,这是不存在的,因为没有second参数。

If you were able to have a constexpr if statement, then this would all work nicely because the compiler wouldn't even compile the bad branch when it evaluates to false, and therefore wouldn't look for a nonexistent function: 如果你能够有一个constexpr if语句,那么这一切都可以正常工作,因为编译器在评估为false时甚至不会编译坏分支 ,因此不会寻找不存在的函数:

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];
}

Demo (requires Clang 3.9.1 or greater and -std=c++1z option.) 演示 (需要Clang 3.9.1或更高版本以及-std=c++1z选项。)

For what it's worth, if you have access to C++17, you can achieve this with a unary right fold : 对于它的价值,如果你有权访问C ++ 17,你可以通过一个正确的折叠实现这一点:

template<typename... T>
decltype(auto) add_elems(size_t i, const T&... elems)
{
    return (elems[i] + ...);
}

Demo 2 (requires Clang 3.6.0 or greater and -std=c++1z option.) 演示2 (需要Clang 3.6.0或更高版本以及-std=c++1z选项。)

Waiting for C++17, I propose a C++11 not-so-nice solution, following the AndyG one 等待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;
 }

As the error message says, the problem you have at the moment is that the call add_elems(i, second, tail...) doesn't match the definition of the function, in the case where tail is empty. 正如错误消息所示,您目前遇到的问题是,如果tail为空,则调用add_elems(i, second, tail...)与函数的定义不匹配。 Even though the boolean expression in the if statement is constexpr, until c++1z the whole body of the function has to be valid. 即使if语句中的布尔表达式是constexpr,直到c ++ 1z,函数的整个主体必须​​是有效的。

@AndyG provides one way that c++1z can deal with this issue, another is with if constexpr , which allows a "compile time branch". @AndyG提供了一种c ++ 1z可以解决这个问题的方法,另一种方法是使用if constexpr ,它允许“编译时分支”。 Either of those allow you to have one (primary) specialisation of your template. 其中任何一个都允许您对模板进行一次(主要)专业化。

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

As many have noted, this is easy in C++1z. 正如许多人所指出的,这在C ++ 1z中很容易。 It can be done in C++14, it is just hard. 它可以在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... );
};

we do a compile time dispatch using pick to one of two lambdas. 我们使用pick到两个lambdas中的一个进行编译时调度。

These lambdas take the part of the code that varies by auto parameter, which makes them templates. 这些lambda采用auto参数变化的代码部分,这使得它们成为模板。 So long as they are valid for some set of auto parameters (even ones they are "never called with"), they are legal C++. 只要它们对某些auto参数(即使它们“永远不会被调用”)有效,它们就是合法的C ++。

What we have readly done is hide the two overloads within the lambdas. 我们已经做过的就是隐藏lambdas中的两个重载。 As C++11 doesn't have template lambdas, this "hidden overload" technique won't work in C++11. 由于C ++ 11没有模板lambdas,这种“隐藏重载”技术在C ++ 11中不起作用。

You can use Boost.Hana to emulate the behaviour of if constexpr in C++14. 您可以使用Boost.Hana来模拟c ++ 14中if constexpr的行为。 For example: 例如:

template <typename...>
struct is_empty_pack : hana::integral_constant<bool, false> {};
template <>
struct is_empty_pack<> : hana::integral_constant<bool, true> {};

template <typename T, typename... Ts>
auto sum(T const& t, Ts const&... ts) {
  return hana::if_(is_empty_pack<Ts...>{},
    [](auto const& t) { return t; },
    [](auto const& t, auto const&... ts) { return t + sum(ts...); }
  )(t, ts...);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM