C++20 概念/需要表达式来测试泛型 lambda 是否接受类型

C++20 concept / requires expression to test if generic lambda accepts type

我正在尝试在编译时验证给定的 lambda 是否接受特定类型(在我的示例代码中为双)。只要 lambda 的签名明确指定了类型就可以了。但是,一旦我在签名中使用带有 auto 的通用 lambda,我就会在评估 requires 语句时遇到编译错误。

下面的代码片段说明了这个问题(also on compiler explorer)

#include <concepts>
#include <iostream>
#include <string>

template<typename F>
concept accepts_double = requires(F f, double d) { f(d); };

struct Component{};

int main(){
    auto f1 = [](double a){double b = a;};
    auto f2 = [](std::string a){std::string b = a;};
    auto f3 = [](auto a){std::string b = a;};

    std::cout << std::boolalpha << accepts_double<decltype(f1)> << "\n"; // expected true
    std::cout << std::boolalpha << accepts_double<decltype(f2)> << "\n"; // expected false

//This one gives the error:
    std::cout << std::boolalpha << accepts_double<decltype(f3)> << "\n"; // expected false, gives compilation error
}

我的印象是 require 语句会验证 f(d);是一个有效的表达式,如果不是,则 return false。但是,在使用最新的 gcc 和 clang 进行编译时,我收到一条错误消息,表明它正在尝试计算函数体:

:13:38: error: no viable conversion from 'double' to 'std::string' (aka 'basic_string<char>')

我的问题是否有不同的方法来确保 lambda 可以传递双精度值,或者 require 语句是否仅限于检查签名?

这是预期的行为。

lambda f3 声称接受任何东西。但是要测试它是否可以用 double 调用,我们需要实例化调用运算符。只有该实例化的“直接上下文”中的失败才算作替换失败——其中直接上下文基本上是与实际函数签名密切相关的任何内容。一旦我们进入调用操作符的主体,它就不再是直接上下文了。我们必须实例化主体,但失败了(无法从 double 构造 string),但这是一个硬编译器错误。

也就是说,这不是SFINAE-friendly。


现在,需要实例化 lambda 主体的唯一原因是因为它 returns auto 并且我们需要知道 return 类型。我们可以直接提供:

auto f3 = [](auto a) -> void {std::string b = a;};

这里,accepts_double<decltype(f3)>编译。但是它给出了true!因为 lambda 确实声称接受一切。这里根本没有限制。我们只是因为碰巧不必实例化主体而免于编译失败。

如果我们想确保这既能编译又能给出正确答案,我们需要添加该约束:

auto f3 = [](std::convertible_to<std::string> auto a) {std::string b = a;};

现在,我们实际上将参数限制为允许的类型集。这既编译又正确地产生 false(因为,当然,double 不能转换为 std::string)。