为什么 std::is_invocable 不能与自动推导出 return 类型的模板化 operator() 一起工作(例如,通用 lambda)
Why doesn't std::is_invocable work with templated operator() which return type is auto-deduced (eg. generic lambdas)
c++17 介绍 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
.
但是,此模板不适用于 模板化运算符 (),其中(直接或间接)return 类型是自动推导的:
#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;
}
我猜可能和SFINAE有关。但这仍然不直观。我试图查看 N4659 draft 中介绍 std::is_invocable
的段落,但仍然找不到有关此行为的更多详细信息。由于本人不是这方面的专家,可能会有疏漏。
I guess the reason may be related to SFINAE.
确实如此。我们称之为 f1
"SFINAE-unfriendly":
struct {
template<typename T>
auto operator()(T t) { return t.a(); }
} f1;
那是因为 f1
宣传自己可以用 anything 调用(完全没有限制)但是要找出调用运算符实际上是什么 returns,你必须实例化调用操作符的主体。这涉及确定表达式 t.a()
的类型,但此时我们处于实例化的“直接上下文”之外。此时的任何失败都不是替换失败 - 这是一个硬编译错误。
f2
另一方面:
struct {
template<typename T>
auto operator()(T t) -> decltype(t.a()) { return t.a(); }
} f2;
是SFINAE-friendly。 t.a()
的检查发生在替换的直接上下文中,因此该表达式中的错误会导致该函数简单地从候选集中删除。 is_invocable
可以检查这个并确定 false
.
f3
与 f1
相同 - 虽然我们在直接上下文中检查 f1(t))
,但 decltype(f1(t))
的实际分辨率仍在直接上下文之外,因此它仍然是一个硬编译器错误。
简短的版本是:除非你是 SFINAE-friendly,否则任何类型特征或概念都不起作用。任何失败都必须在直接上下文中。
c++17 介绍 template <class Fn, class...ArgTypes> struct is_invocable
:
Determines whether
Fn
can be invoked with the argumentsArgTypes...
. Formally, determines whetherINVOKE(declval<Fn>(), declval<ArgTypes>()...)
is well formed when treated as an unevaluated operand, whereINVOKE
is the operation defined inCallable
.
但是,此模板不适用于 模板化运算符 (),其中(直接或间接)return 类型是自动推导的:
#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;
}
我猜可能和SFINAE有关。但这仍然不直观。我试图查看 N4659 draft 中介绍 std::is_invocable
的段落,但仍然找不到有关此行为的更多详细信息。由于本人不是这方面的专家,可能会有疏漏。
I guess the reason may be related to SFINAE.
确实如此。我们称之为 f1
"SFINAE-unfriendly":
struct {
template<typename T>
auto operator()(T t) { return t.a(); }
} f1;
那是因为 f1
宣传自己可以用 anything 调用(完全没有限制)但是要找出调用运算符实际上是什么 returns,你必须实例化调用操作符的主体。这涉及确定表达式 t.a()
的类型,但此时我们处于实例化的“直接上下文”之外。此时的任何失败都不是替换失败 - 这是一个硬编译错误。
f2
另一方面:
struct {
template<typename T>
auto operator()(T t) -> decltype(t.a()) { return t.a(); }
} f2;
是SFINAE-friendly。 t.a()
的检查发生在替换的直接上下文中,因此该表达式中的错误会导致该函数简单地从候选集中删除。 is_invocable
可以检查这个并确定 false
.
f3
与 f1
相同 - 虽然我们在直接上下文中检查 f1(t))
,但 decltype(f1(t))
的实际分辨率仍在直接上下文之外,因此它仍然是一个硬编译器错误。
简短的版本是:除非你是 SFINAE-friendly,否则任何类型特征或概念都不起作用。任何失败都必须在直接上下文中。