我正在尝试一些lambda检测功能。
我试图实现的是能够检测我是否可以用const C&
类型的参数调用lambda F,返回可转换为bool
的结果。
下面的代码看起来很接近我所需要的,但并没有像预期的那样运行:
#include <string>
#include <iostream>
#include <type_traits>
#include <experimental/type_traits>
template <typename F, typename C>
using is_invocable_predicate = decltype(std::declval<F>()(std::declval<const C&>()));
template <typename F, typename C>
constexpr bool can_invoke_pred_v = std::experimental::is_detected_convertible_v<bool, is_invocable_predicate, F, C> ;
int main() {
// this lambda expectedly can not be invoked with std::string argument
auto lambda = [](auto x) -> bool { return x * x; };
lambda(1); // works
//lambda(std::string("abc")); // this obviously does not compile (as there is no operator * for std::string
constexpr auto can_invoke_int = can_invoke_pred_v<decltype(lambda), int>;
static_assert(can_invoke_int); // invocable with int
// if I remove ->bool in lambda definition next line will not compile,
// otherwise the assertion fails
constexpr auto can_invoke_str = can_invoke_pred_v<decltype(lambda), std::string>;
static_assert(not can_invoke_str); // expected to fail as I cannot invoke the lambda with string
return 0;
}
如果我删除-> bool
(比如我将lambda定义为auto lambda = [](auto x) { return x * x; };
),那么static_assert(can_invoke_pred_v<decltype(lambda), std::string>);
行将无法编译,即而不是检测到这样的lambda包含x*x
表达式不能为类型std::string生成,它被生成,然后我得到编译错误。
test.cpp:14:41: error: no match for 'operator*' (operand types are 'std::__cxx11::basic_string<char>' and 'std::__cxx11::basic_string<char>')
14 | auto lambda = [](auto x) { return x * x; };
| ~~^~~
是否有解决此问题的方法?有人能解释一下这里发生了什么吗?
2条答案
按热度按时间yx2lnoni1#
既然你添加了
not
,我不明白为什么你会期望它失败。相反,它现在失败表明is_invocable_predicate
认为它可以用std::string
参数调用lambda。你的lambda不是SFINAE友好的。SFINAE不能拦截lambda体内部产生的错误,所以如果检查了错误体,就会得到一个硬错误,这就是删除
-> bool
时发生的错误。当lambda主体没有被检查时,
can_invoke_str
会默默地返回true,因为没有任何关于[](auto x) -> bool
部分的信息表明std::string
不能传递给它。为什么
-> bool
在的时候尸体没有被检查?默认情况下不检查它,但是当没有指定返回类型(或者按照auto
指定)时,必须检查主体以确定真正的返回类型。如何解决这个问题?
选项1:
[](auto x) -> decltype(x * x) { return x * x; }
。现在SFINAE将检查[](auto x) -> decltype(x * x)
部分,将std::string
代入x * x
将触发SFINAE。但是这将返回类型从
bool
更改为x * x
返回的任何类型。选项2:
[](auto x) -> bool requires requires{x * x;} { return x * x; }
第一个
requires
接受右边的一个布尔表达式,它决定了函数是否可以用这些模板参数调用。requires{x * x;}
返回true
或false
,具体取决于x * x
是否有效。这使您可以指定所需的任何返回类型。
requires
需要C20。选项3:Pre-C20我们以前这样做:
dependent_type<bool, decltype(x * x)>
只是bool
,但是第二个模板参数必须由SFINAE检查。uurv41yg2#
在C++中,并不是所有的模板替换错误都能被检测到。
为了使编译器编写者的工作不那么复杂,在解析函数体时发现的错误不参与SFINAE(替换失败不是错误)。
当你写的时候
这将生成一个类,大致如下所示:
在
operator()
方法体中发现的任何错误都将是 * 硬 * 错误,无法恢复。编译器将忽略该错误并停止编译。错误的某个子集是“替换失败”友好的(aka,SFINAE)。虽然标准从技术上描述了它们,但它们基本上是作为模板“签名”的一部分而不是模板“主体”发生的错误。
如果你的
auto
lambda(或其他模板)不做使自己成为SFINAE的工作,那么就没有办法确定用特定类型示例化模板(或向其传递某些类型)是否安全,而不会冒着硬错误的风险。我的经典做法是
其使用方式如下:
并生成(单语句)SFINAE友好的lambda。函数也可以通过使用
auto
返回类型来使用它。