首页 > 解决方案 > 使用概念检测空参数包

问题描述

回答我发布的另一个问题时,Jack Harwood 分享了一个很好的解决方案,可以使用概念检测空的可变参数包。示例问题是使用递归计算参数包参数的数量。我在下面复制了他的解决方案。

template <typename... Args>
concept NonVoidArgs = sizeof...(Args) > 0;

template <typename... Args>
concept VoidArgs = sizeof...(Args) == 0;

template <VoidArgs...>
constexpr int NumArguments() {
    return 0;
}

template<typename FirstArg, NonVoidArgs... RemainingArgs>
constexpr int NumArguments() {
    return 1 + NumArguments<RemainingArgs...>();
}

例子:

int main() {
    std::cout << NumArguments<int>() << std::endl; // 1
    std::cout << NumArguments() << std::endl; // 0
    std::cout << NumArguments<float, int, double, char>() << std::endl; // 4
    return 0;
}

我认为这是一个比使用类模板专门化功能模板更好的解决方案。但是,我不确定它为什么会起作用。模板函数

template<typename FirstArg, NonVoidArgs... RemainingArgs>
constexpr int NumArguments()

似乎至少需要两个模板参数。需要有 a FirstArg,然后至少有 1 RemainingArgs。当只有一个模板参数时,为什么编译器会调用这个重载(?)?此行为是否会对此解决方案产生任何问题?

标签: c++recursionvariadic-templatesc++20c++-concepts

解决方案


这些概念本身是正确的,但问题是该示例错误地使用了它们并且本身就是错误的。在给定的代码中,概念是基于参数而不是整个参数包。


您当前的版本

template<typename FirstArg, NonVoidArgs... RemainingArgs>
constexpr int NumArguments() {
  return 1 + NumArguments<RemainingArgs...>();
}

实际上相当于一个requires子句和一个折叠表达式

template<typename FirstArg, typename... RemainingArgs>
int NumArguments() requires (NonVoidArgs<RemainingArgs> && ...) {
  return 1 + NumArguments<RemainingArgs...>();
}

这实际上意味着您正在定义一个必须有参数的函数,FirstArg并且可能有一个任意大小的附加参数包RemainingArgs(包括零!)。这些参数中的每一个 - 如果存在 - 然后独立检查它是否满足NonVoidArgs当然总是正确的概念。这意味着函数基本上退化为

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

话虽如此,参数包实际上甚至没有必要NonVoidArgs在这里试试!

不仅对单个模板参数的数据类型而且对整个参数包施加限制的正确方法实际上是一个requires子句,如下所示:

template<typename FirstArg, typename... RemainingArgs>
int NumArguments() requires NonVoidArgs<RemainingArgs...> {
  return 1 + NumArguments<RemainingArgs...>();
}

正如预期的那样,这不起作用:在这里试试!我想编写代码的人很幸运,因为他们没有正确执行这些概念,所以它实际上可以工作。这个概念背后的基本思想是有缺陷的。


推荐阅读