让 SFINAE 在重载的函数对象上使用 `is_callable`

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

考虑以下函数对象l

auto l = [](auto x){ x.foo(); };

我可以成功地 static_assert 它可以被具有 .foo() 成员函数的类型调用:

struct Foo { void foo(); };
static_assert(std::is_callable<decltype(l)(Foo)>{});

如果我使用的类型没有 .foo() 成员函数,我现在希望 std::is_callable 求值为 std::false_type

static_assert(!std::is_callable<decltype(l)(int)>{});

不幸的是,上面的static_assert导致编译错误:

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(); };
                      ~~^~~

我也尝试使用 std::void_t 实现我自己的 is_callable,如下所示,得到相同的编译错误:

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 { };

我的印象是,如果 std::void_t<decltype(/* ... */)> 中的表达式由于 SFINAE 而无效,则会选择 std::false_type 回退。

live example on wandbox (C++14 compliant)

您的 lambda 不限制它与 sfinae 的参数。您的 lambda 可以用任何东西调用,但在使用这些参数实例化时会触发硬编译错误。

要获得预期效果,请对 return 类型施加约束:

auto l = [](auto x) -> void_t<decltype(x.foo())> { x.foo(); };

这样,is_callable 特性将产生正确的结果。

Why is this SFINAE not taking place here, resulting in a compilation error?

因为 l 可以用任何东西调用 - 编译错误发生在它的主体被实例化时,它发生在 "isolated environment" SFINAE 正在使用的地方之外地点。

约束 lambda 将解决问题:

auto l = [](auto x) -> void_t<decltype(x.foo())> { x.foo(); };

(请注意,此处使用 void_t,因为 lambda 未返回 x.foo()。)


How can I achieve my desired behavior? (i.e. evaluate to std::false_type if calling the overloaded function object is ill-formed)

如果函数对象的签名没有得到适当的约束,将无法实现您想要的行为

要做到这一点,需要编译器实现某种 "speculative compilation",由于实现难度大,这是不可取的。

本文中的更多信息:"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.

并且在此讨论中:"Asserting an expected compile-time failure"