首页 > 解决方案 > 为没有模板参数的可变参数模板递归创建基本案例

问题描述

我正在尝试对可变参数模板使用递归。我希望基本案例的模板参数为零。在查看了stackoverflow对之前问题的回答后,我发现了对这个问题的两种回答:

  1. 您不应该专门化模板功能。Herb Sutter 在这里写道:http ://www.gotw.ca/publications/mill17.htm
  2. 您使用template <typename = void> template <typename T = void> 。比如这里的第一个答案:How to write a variadic template recursive function?

我尝试在我的问题中使用解决方案 (2),但收到错误。这是一个最小的、可重现的例子:

#include <iostream>

template<typename = void> // base case
int NumArguments() {
    return 0;
}

template<typename FirstArg, typename... RemainingArgs>
int NumArguments() {
    return 1 + NumArguments<RemainingArgs...>();
}
class A {
public:
    A() {}
};

int main() {
    std::cout << NumArguments<A>();
    return 0;
}

Microsoft Visual C++20 中的编译给出了错误:

 error C2668: 'NumArguments': ambiguous call to overloaded function
 message : could be 'int NumArguments<A,>(void)'
message : or       'int NumArguments<A>(void)'
message : while trying to match the argument list '()'

这个错误信息是什么意思?如何使用可变参数模板为递归创建零参数基本案例?

编辑:评论中有要求更完整地描述我的问题。这个问题实际上是问题的标题,而不是“我如何让我的代码工作?”,但我还没有让我的代码编译,所以我决定分享它。

NumArguments是另一个函数的替代,ComputeSize它接受输入Args并返回一个std::size_t.

    template<typename = void>
    constexpr std::size_t ComputeSize() {
        return 0;
    }

    template<typename FirstArg, typename... RemainingArgs>
    constexpr std::size_t ComputeSize() {
        return FuncReturnSize<FirstArg>() + ComputeSize<RemainingArgs...>(); 
    }

Args in的可能列表Args是有限的并且在编译之前是已知的。FuncReturnSize对这些中的每一个都超载Args。例如,两个可能的“重载”(?)是

template <typename T>
requires ((requires (T t) { { t.Func()} -> std::same_as<double>; }) || (requires (T t) { { t.Func() } -> std::same_as<std::vector<double>>; }))
constexpr std::size_t FuncReturnSize() {
    return 1;
}

template <typename T>
requires requires (T t) { { t.Func() } -> is_std_array_concept<>; }
constexpr std::size_t FuncReturnSize() {
    return std::tuple_size_v<decltype(std::declval<T&>().Func())>;
}

这个概念is_std_array_concept<>应该检查返回值是否t.Func()是某个大小的数组。我还不确定它是否有效。它定义为

    template<class T>
    struct is_std_array : std::false_type {};
    template<class T, std::size_t N>
    struct is_std_array<std::array<T, N>> : std::true_type {};
    template<class T>
    struct is_std_array<T const> : is_std_array<T> {};
    template<class T>
    struct is_std_array<T volatile> : is_std_array<T> {};
    template<class T>
    struct is_std_array<T volatile const> : is_std_array<T> {};

    template<typename T>
    concept is_std_array_concept = is_std_array<T>::value;

我希望所有这些计算都在编译时完成,所以我定义了

template<std::size_t N>
std::size_t CompilerCompute() {
    return N;
}

我现在应该能够ComputeSize在编译时像这样:

CompilerCompute<ComputeSize<Args...>()>()

标签: c++recursioncompiler-errorsvariadic-templatesconstexpr

解决方案


错误消息的意思正是它所说的,调用是模棱两可的。

template<typename = void> // base case
constexpr int NumArguments() {
    return 0;
}

这不是一个接受 0 个参数的模板函数,这是一个接受一个默认参数的模板函数(因此,如果未指定参数,则它是无效的)。这意味着这NumArguments<A>()是对该函数的完全有效的调用。

但是,NumArguments<A>()这也是对带有空可变参数包的可变参数重载的完全有效调用(NumArguments<A,>()错误消息中列出的重载)。

您的案例与链接示例的不同之处在于,在链接示例中,可变参数重载是在ints 上模板化的,而不是在类型上,所以那里没有歧义。我在这里复制了该实现:

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

注意,第二个重载f是一个可变参数模板,其中每个模板参数都必须是一个int值。如果 A 是一种类型,则调用f<A>()将不会匹配该重载,因此可以避免歧义。

不可能声明一个零参数的模板函数,所以你不走运。但是,您可以将其转换为类模板,因为类模板可以部分专门化。

template <class ...Args> 
struct NumArguments;

template <>
struct NumArguments<> {
    static constexpr int value = 0;
};

template <class T, class ...Args>
struct NumArguments<T, Args...> {
    static constexpr int value = 1 + NumArguments<Args...>::value;
};

当然,这个特定的实现可以通过简化来使用sizeof...,但是 OP 已经表明它们的实际用例更加复杂。


推荐阅读