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
)。
我正在尝试在编译时验证给定的 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
)。