c++ - 为没有模板参数的可变参数模板递归创建基本案例
问题描述
我正在尝试对可变参数模板使用递归。我希望基本案例的模板参数为零。在查看了stackoverflow对之前问题的回答后,我发现了对这个问题的两种回答:
- 您不应该专门化模板功能。Herb Sutter 在这里写道:http ://www.gotw.ca/publications/mill17.htm
- 您使用
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...>();
}
Arg
s 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...>()>()
解决方案
错误消息的意思正是它所说的,调用是模棱两可的。
template<typename = void> // base case
constexpr int NumArguments() {
return 0;
}
这不是一个接受 0 个参数的模板函数,这是一个接受一个默认参数的模板函数(因此,如果未指定参数,则它是无效的)。这意味着这NumArguments<A>()
是对该函数的完全有效的调用。
但是,NumArguments<A>()
这也是对带有空可变参数包的可变参数重载的完全有效调用(NumArguments<A,>()
错误消息中列出的重载)。
您的案例与链接示例的不同之处在于,在链接示例中,可变参数重载是在int
s 上模板化的,而不是在类型上,所以那里没有歧义。我在这里复制了该实现:
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 已经表明它们的实际用例更加复杂。
推荐阅读
- layout - 聚焦时门户行可编辑字段表现奇怪(FileMaker Pro 18)
- java - xml中的*替换表示任何字符串是什么
- wordpress - {多个项目} Woocommerce
- video-streaming - 如何对 H265/HEVC 的 RTP 数据(通过 UDP)中的碎片帧进行解包?
- javascript - 使用“import * as”和解构时的意外行为
- html - 如何在 Angular 8 中获取 Windows 用户名
- ios - 搜索具有私有 API 类名的子视图会被拒绝吗?
- binding - SwiftUI 我可以使用 Binding 和 @Binding 属性包装器设置自定义绑定吗?
- amazon-web-services - 属于不同 VPC 的子网之间的 AWS 路由问题
- python - 多对多加入行为