【问题标题】:Using concepts to detect empty parameter packs使用概念检测空参数包
【发布时间】:2021-09-03 14:38:28
【问题描述】:

在我发布的 answeranother question 中,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()

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

【问题讨论】:

  • 我看不出那些conceptsNumArguments 函数与仅仅做有什么不同:template&lt;typename... Args&gt; constexpr int NumArgs() { return sizeof...(Args); }
  • @Brandon NumArgs 只是一个例子。在我的程序中,我发现我想使用参数包进行递归。由于一些关于专门模板函数的规则(我认为?),我在编写基本案例时遇到了麻烦。 VoidArgs 概念允许我用概念重载函数而不是专门化(我认为?)。 NumArgs 只是一个例子,这个问题是关于意外行为的。
  • @Brandon 但是是的,NumArgs 应该完全按照你说的做。

标签: c++ recursion variadic-templates c++20 c++-concepts


【解决方案1】:

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


您当前的版本

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...>();
}

话虽如此,参数包实际上甚至不需要NonVoidArgsTry it here!

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 2016-09-07
    • 2021-04-17
    • 1970-01-01
    • 2020-10-11
    相关资源
    最近更新 更多