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

How to write a variadic template recursive function?

本文关键字:递归函数 何编写      更新时间:2023-10-16

我正在尝试编写一个可变模板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<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>可以通过"扩展"到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>
{};

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

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 s就可以了。

只需按照通常的方式进行总结即可。

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

使用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;
}