如何编写可变参数模板递归函数?

How to write a variadic template recursive function?

我正在尝试编写一个可变参数模板 constexpr 函数来计算给定模板参数的总和。这是我的代码:

template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}

template<int First>
constexpr int f()
{
    return First;
}

int main()
{
    f<1, 2, 3>();
    return 0;
}

不幸的是,在尝试解析 f<3,>() 调用时,它不会编译并报告错误消息 error C2668: 'f': ambiguous call to overloaded function

我还尝试更改我的递归基本情况以接受 0 个模板参数而不是 1 个:

template<>
constexpr int f()
{
    return 0;
}

但此代码也无法编译(消息 error C2912: explicit specialization 'int f(void)' is not a specialization of a function template)。

我可以提取第一个和第二个模板参数来编译和工作,如下所示:

template<int First, int Second, int... Rest>
constexpr int f()
{
    return First + f<Second, Rest...>();
}

但这似乎不是最好的选择。那么,问题来了:如何优雅的写出这个计算?

UP: 我也试过把这个写成一个函数:

template<int First, int... Rest>
constexpr int f()
{
    return sizeof...(Rest) == 0 ? First : (First + f<Rest...>());
}

这也行不通:error C2672: 'f': no matching overloaded function found

按照通常的方式总结一下。

template<int... Args>
constexpr int f() {
   int sum = 0;
   for(int i : { Args... }) sum += i;
   return sum;
}

我发现将代码从模板参数移动到函数参数通常更容易:

constexpr int sum() { return 0; }

template <class T, class... Ts>
constexpr int sum(T value, Ts... rest) {
    return value + sum(rest...);
}

如果您确实希望它们作为模板参数,您可以让 f 只需通过将它们向下移动来调用 sum

template <int... Is>
constexpr int f() {
    return sum(Is...);
}

constexpr,所以用int就可以了。

你的基本情况是错误的。您需要一个空列表的案例,但正如编译器所建议的那样,您的第二次尝试不是有效的模板专业化。为零参数定义有效实例化的一种方法是创建一个接受空列表的重载

template<class none = void>
constexpr int f()
{
    return 0;
}
template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}
int main()
{
    f<1, 2, 3>();
    return 0;
}

编辑:为了完整起见,也是我的第一个答案,@alexeykuzmin0 通过添加条件修复:

template<int First=0, int... Rest>
constexpr int f()
{
    return sizeof...(Rest)==0 ? First : First + f<Rest...>();
}
template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}

template<int First>
constexpr int f()
{
    return First;
}

int main()
{
    f<1, 2, 3>();
    return 0;
}

您收到此错误:

error C2668: 'f': ambiguous call to overloaded function while trying to resolve f<3,>() call.

这是因为可变参数包可以被赋予 0 个参数,所以 f<3> 可以通过 "expanding" 到 template<3, >template<int First, int... Rest> 一起工作。但是,你也有template<int First>的特化,所以编译器不知道选择哪一个。

明确说明第一个和第二个模板参数是解决此问题的完全有效且好的解决方案。


当您尝试将基本情况更改为:

template <>
constexpr int f()
{
    return 0;
}

你遇到了问题,因为函数不能以那种方式专门化。 类 和结构可以,但函数不行。


解决方案 #1:使用 constexpr

的 C++17 折叠表达式
template <typename... Is>
constexpr int sum(Is... values) {
    return (0 + ... + values);
}

解决方案 #2:使用 constexpr 函数

constexpr int sum() {
    return 0;
}

template <typename I, typename... Is>
constexpr int sum(I first, Is... rest) {
    return first + sum(rest...);
}

解决方案 #3:使用模板元编程

template <int... Is>
struct sum;

template <>
struct sum<>
    : std::integral_constant<int, 0>
{};

template <int First, int... Rest>
struct sum<First, Rest...>
    : std::integral_constant<int,
        First + sum_impl<Rest...>::value>
{};

使用 std::initializer_list 的更通用的解决方案是:

template <typename... V>                                                                                                                                                                                         
auto sum_all(V... v)
{
  using rettype = typename std::common_type_t<V...>;
  rettype result{};
  (void)std::initializer_list<int>{(result += v, 0)...};
  return result;
}

这与上面@T.C建议的非常相似:

#include<iostream>
#include<numeric>

template<int First, int... Rest>
constexpr int f()
{
    auto types = {Rest...};
    return std::accumulate(std::begin(types), std::end(types),0);   
}

int main()
{
    std::cout <<f<1, 2, 3>();
    return 0;
}