Reputation: 93264
Consider the following function object l
:
auto l = [](auto x){ x.foo(); };
I can successfully static_assert
that it is callable with a type that has a .foo()
member function:
struct Foo { void foo(); };
static_assert(std::is_callable<decltype(l)(Foo)>{});
I would now like std::is_callable
to evaluate to std::false_type
if I use a type that does not have a .foo()
member function:
static_assert(!std::is_callable<decltype(l)(int)>{});
Unfortunately, the above static_assert
results in a compilation error:
prog.cc: In instantiation of '<lambda(auto:1)> [with auto:1 = int]':
prog.cc:8:44: required by substitution of 'template<class TF, class ... Ts>
struct is_callable<TF(Ts ...),
std::void_t<decltype (declval<TF>()((declval<Ts>)()...))>
> [with TF = <lambda(auto:1)>; Ts = {int}]'
prog.cc:17:50: required from here
prog.cc:12:24: error: request for member 'foo' in 'x', which is of non-class type 'int'
auto l = [](auto x){ x.foo(); };
~~^~~
I also tried implementing my own is_callable
by using std::void_t
as follows, getting the same compilation error:
template <typename, typename = void>
struct is_callable : std::false_type { };
template <typename TF, class... Ts>
struct is_callable<TF(Ts...),
std::void_t<decltype(std::declval<TF>()(std::declval<Ts>()...))>>
: std::true_type { };
I was under the impression that the std::false_type
fallback would be chosen if the expression inside std::void_t<decltype(/* ... */)>
was not valid due to SFINAE.
Why is this SFINAE not taking place here, resulting in a compilation error?
How can I achieve my desired behavior? (i.e. evaluate to std::false_type
if calling the overloaded function object is ill-formed)
is_callable
check.live example on wandbox (C++14 compliant)
Upvotes: 3
Views: 385
Reputation: 93264
Why is this SFINAE not taking place here, resulting in a compilation error?
Because l
is callable with everything - the compilation error occurs when its body is instantiated, which happens outside of the "isolated environment" where SFINAE is taking place.
Constraining the lambda as shown by Guillaume Racicot's answer will fix the issue:
auto l = [](auto x) -> void_t<decltype(x.foo())> { x.foo(); };
(Note that void_t
is being used here as x.foo()
is not being returned by the lambda.)
How can I achieve my desired behavior? (i.e. evaluate to
std::false_type
if calling the overloaded function object is ill-formed)
If the function objects' signatures are not properly constrained, there is no way of achieving your desired behavior.
Being able to do that would require compilers to implement some sort of "speculative compilation" which is undesirable due to the difficulty of implementation.
More information in this article: "Diagnosable validity".
There is no way to check it from within C++, and there is not going to be any: it has been clearly laid out, that compiler vendors cannot be forced to implement a “speculative compilation” and backtracking from arbitrarily deep template instantiation failures.
And in this discussion: "Asserting an expected compile-time failure".
Upvotes: 1
Reputation: 41760
Your lambda does not constrain it's arguments with sfinae. Your lambda is callable with anything, but triggers hard compilation errors when instantiated with these arguments.
To get the desired effect, put constraints on the return type:
auto l = [](auto x) -> void_t<decltype(x.foo())> { x.foo(); };
That way, the is_callable
trait will yield the correct result.
Upvotes: 5