Vittorio Romeo Vittorio Romeo - 9 months ago 56
C++ Question

Getting SFINAE to work with `is_callable` on an overloaded function object

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)



live example on wandbox (C++14 compliant)

Answer Source

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.