C++20概念/需要表达式来测试泛型lambda是否接受类型
我试图在编译时验证给定的lambda是否接受某种类型(在我的示例代码中为Double)。只要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)> << "
"; // expected true
std::cout << std::boolalpha << accepts_double<decltype(f2)> << "
"; // expected false
//This one gives the error:
std::cout << std::boolalpha << accepts_double<decltype(f3)> << "
"; // expected false, gives compilation error
}
我的印象是,Requires语句将验证f(D);是否为有效表达式,否则将返回FALSE。但是,当使用最新的GCC和clang进行编译时,我得到一个错误,指示它正在尝试计算函数体:
:13:38: error: no viable conversion from 'double' to 'std::string' (aka 'basic_string<char>')
我的问题有没有其他方法可以确保向lambda传递双精度值,或者是否将Requires语句限制为仅检查签名?
解决方案
这是预期行为。
lambdaf3
声称接受任何。但是要测试它是否可以通过double
调用,我们需要实例化调用操作符。只有该实例化的直接上下文中的失败才算作替换失败--其中直接上下文基本上是与实际函数签名密切相关的任何东西。一旦我们进入调用操作符的主体,这就不再是直接的上下文了。我们必须实例化Body,这会失败(无法从double
构造string
),但这是一个严重的编译器错误。
换句话说,这对SFINAE不友好。
现在,需要实例化lambda主体的唯一原因是它返回
auto
,而我们需要知道返回类型。我们可以直接提供: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
)。
相关文章