I am experimenting with some lambda detection functionality.
What I am trying to achieve is to be able to detect if I can call a lambda F with an argument of type const C& returning a result convertible to bool.
The code below looks close to what I need, but does not behave as expected :
#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;
}
If I remove -> bool (say I define lambda as auto lambda = [](auto x) { return x * x; }; ), then line static_assert(can_invoke_pred_v<decltype(lambda), std::string>); does not compile, i.e. instead of detecting that such lambda containing x*x expression cannot be generated for type std::string, it is generated and then I get compilation error.
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; };
| ~~^~~
Is there a solution for this problem? Could someone explain what is happening here?
Since you added
not, I don't see why you'd expect it to fail. Instead, it failing now indicates thatis_invocable_predicatethinks it can invoke your lambda with astd::stringargument.Your lambda is not SFINAE-friendly. SFINAE can't intercept an error originating inside the lambda body, so if the faulty body is examined at all, you get a hard error, which is what happened when you removed
-> bool.When the lambda body is not examined,
can_invoke_strsilently returns true, because nothing about the[](auto x) -> boolpart indicates thatstd::stringcan't be passed to it.Why is the body not examined when
-> boolis there? It's not examined by default, but when the return type is not specified (or specified in terms ofauto), the body has to be examined to determine the true return type.How to fix this?
Option 1:
[](auto x) -> decltype(x * x) { return x * x; }. Now SFINAE will examine the[](auto x) -> decltype(x * x)part, and substitutingstd::stringintox * xwill trigger SFINAE.But this changes the return type from
boolto whateverx * xreturns.Option 2:
[](auto x) -> bool requires requires{x * x;} { return x * x; }The first
requiresaccepts a boolean expression on the right, which determines whether the function is callable with those template arguments or not.requires{x * x;}returns eithertrueorfalsedepending onx * xbeing valid.This lets you specify any return type you want.
requiresneeds C++20.Option 3: Pre-C++20 we used to do this:
dependent_type<bool, decltype(x * x)>is justbool, but the second template argument has to be checked by SFINAE.