c++ 检测带有某些参数的泛型lambda是否可调用

9lowa7mx  于 2023-06-25  发布在  其他
关注(0)|答案(2)|浏览(218)

我正在尝试一些lambda检测功能。
我试图实现的是能够检测我是否可以用const C&类型的参数调用lambda F,返回可转换为bool的结果。
下面的代码看起来很接近我所需要的,但并没有像预期的那样运行:

  1. #include <string>
  2. #include <iostream>
  3. #include <type_traits>
  4. #include <experimental/type_traits>
  5. template <typename F, typename C>
  6. using is_invocable_predicate = decltype(std::declval<F>()(std::declval<const C&>()));
  7. template <typename F, typename C>
  8. constexpr bool can_invoke_pred_v = std::experimental::is_detected_convertible_v<bool, is_invocable_predicate, F, C> ;
  9. int main() {
  10. // this lambda expectedly can not be invoked with std::string argument
  11. auto lambda = [](auto x) -> bool { return x * x; };
  12. lambda(1); // works
  13. //lambda(std::string("abc")); // this obviously does not compile (as there is no operator * for std::string
  14. constexpr auto can_invoke_int = can_invoke_pred_v<decltype(lambda), int>;
  15. static_assert(can_invoke_int); // invocable with int
  16. // if I remove ->bool in lambda definition next line will not compile,
  17. // otherwise the assertion fails
  18. constexpr auto can_invoke_str = can_invoke_pred_v<decltype(lambda), std::string>;
  19. static_assert(not can_invoke_str); // expected to fail as I cannot invoke the lambda with string
  20. return 0;
  21. }

如果我删除-> bool(比如我将lambda定义为auto lambda = [](auto x) { return x * x; };),那么static_assert(can_invoke_pred_v<decltype(lambda), std::string>);行将无法编译,即而不是检测到这样的lambda包含x*x表达式不能为类型std::string生成,它被生成,然后我得到编译错误。

  1. test.cpp:14:41: error: no match for 'operator*' (operand types are 'std::__cxx11::basic_string<char>' and 'std::__cxx11::basic_string<char>')
  2. 14 | auto lambda = [](auto x) { return x * x; };
  3. | ~~^~~

是否有解决此问题的方法?有人能解释一下这里发生了什么吗?

yx2lnoni

yx2lnoni1#

  1. static_assert(not can_invoke_str); // expected to fail as I cannot invoke the lambda with string

既然你添加了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;}返回truefalse,具体取决于x * x是否有效。
这使您可以指定所需的任何返回类型。
requires需要C20。
选项3:Pre-C
20我们以前这样做:

  1. template <typename T, typename...>
  2. struct dependent_type_helper {using type = T;};
  3. template <typename T, typename ...P>
  4. using dependent_type = typename dependent_type_helper<T, P...>::type;
  1. [](auto x) -> dependent_type<bool, decltype(x * x)> { return x * x; }

dependent_type<bool, decltype(x * x)>只是bool,但是第二个模板参数必须由SFINAE检查。

展开查看全部
uurv41yg

uurv41yg2#

在C++中,并不是所有的模板替换错误都能被检测到。
为了使编译器编写者的工作不那么复杂,在解析函数体时发现的错误不参与SFINAE(替换失败不是错误)。
当你写的时候

  1. [](auto x){return x*x;}

这将生成一个类,大致如下所示:

  1. struct anonymous_lambda {
  2. template<class T>
  3. auto operator()(T x)const{ return x*x; }
  4. };

operator()方法体中发现的任何错误都将是 * 硬 * 错误,无法恢复。编译器将忽略该错误并停止编译。
错误的某个子集是“替换失败”友好的(aka,SFINAE)。虽然标准从技术上描述了它们,但它们基本上是作为模板“签名”的一部分而不是模板“主体”发生的错误。
如果你的auto lambda(或其他模板)不做使自己成为SFINAE的工作,那么就没有办法确定用特定类型示例化模板(或向其传递某些类型)是否安全,而不会冒着硬错误的风险。
我的经典做法是

  1. #define RETURNS(...) ->decltype(__VA_ARGS__) { return __VA_ARGS__; }

其使用方式如下:

  1. [](auto x) RETURNS(x*x)

并生成(单语句)SFINAE友好的lambda。函数也可以通过使用auto返回类型来使用它。

展开查看全部

相关问题