Why doesn't std::is_invocable work with templated operator() which return type is auto-deduced (eg. generic lambdas)

女生的网名这么多〃 提交于 2021-02-08 09:47:36

问题


c++17 introduces template <class Fn, class...ArgTypes> struct is_invocable:

Determines whether Fn can be invoked with the arguments ArgTypes.... Formally, determines whether INVOKE(declval<Fn>(), declval<ArgTypes>()...) is well formed when treated as an unevaluated operand, where INVOKE is the operation defined in Callable.

However, this template does not work with templated operator() which (direct or indirect) return type is auto-deduced:

#include <type_traits>
#include <iostream>

struct A {
  int a() { return 1; }
};

struct B {};

struct {
  template<typename T>
  auto operator()(T t) { return t.a(); }
} f1;

struct {
  template<typename T>
  auto operator()(T t) -> decltype(t.a()) { return t.a(); }
} f2;

struct {
  template<typename T>
  auto operator()(T t) -> decltype(f1(t)) { return f1(t); }
} f3;

template<typename F, typename T>
void check(F&& f, T) {
  std::cout << std::boolalpha << std::is_invocable_v<F, T> << std::endl;
}

int main() {
  check(f1, A());   // true
  check(f2, A());   // true
  check(f3, A());   // true
  //check(f1, B()); // error: ‘struct B’ has no member named ‘a’
  check(f2, B());   // false
  //check(f3, B()); // error: ‘struct B’ has no member named ‘a’
  return 0;
}

I guess the reason may be related to SFINAE. But this is still not intuitive. I tried to check N4659 draft's paragraphs which introduced std::is_invocable, but still couldn't find more detail about this behaivor. Since I am not an expert in this area, there may be omissions.


回答1:


I guess the reason may be related to SFINAE.

Indeed. We call things like f1 "SFINAE-unfriendly":

struct {
  template<typename T>
  auto operator()(T t) { return t.a(); }
} f1;

That's because f1 advertises itself as being invocable with anything (there are no constraints at all) but to find out what the call operator actually returns, you have to instantiate the body of the call operator. That involves determining the type of the expression t.a(), but at this point we're outside of the "immediate context" of the instantiation. Any failure at this point is not a substitution failure - it's a hard compile error.

f2 on the other hand:

struct {
  template<typename T>
  auto operator()(T t) -> decltype(t.a()) { return t.a(); }
} f2;

is SFINAE-friendly. The check for t.a() happens in the immediate context of the substitution, so an error in that expression leads to the function simply being removed from the candidate set. is_invocable can check this and determine false.

f3 is the same as f1 - while we check f1(t)) in the immediate context, the actual resolution of decltype(f1(t)) is still outside the immediate context, so it's still a hard compiler error.


The short version is: no type traits or concepts work unless you're SFINAE-friendly. Any failures must be in the immediate context.



来源:https://stackoverflow.com/questions/64186621/why-doesnt-stdis-invocable-work-with-templated-operator-which-return-type-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!