为什么std::is_invocable不能处理转发?
Why can std::is_invocable not handle forwarding?
我有一个 class,它只是将函数调用转发给另一个 class,我希望能够在我的转发 class 上使用 std::invocable<>
。但由于某种原因失败了……这是我应该期待的吗?有解决办法吗?
#include <type_traits>
#include <utility>
struct Foo {
constexpr int operator()( int i ) const {
return i;
}
};
struct ForwardToFoo {
template<class ...Args>
constexpr decltype(auto) operator()( Args &&...args ) const {
Foo foo;
return foo( std::forward<Args>( args )... );
}
};
int main( void ) {
// These work fine
static_assert( std::is_invocable_v<ForwardToFoo, int> == true );
static_assert( std::is_invocable_v<Foo, int> == true );
static_assert( std::is_invocable_v<Foo> == false );
// This causes a compile error
static_assert( std::is_invocable_v<ForwardToFoo> == false );
return 0;
}
编辑:
到目前为止的答案表明问题是最后一个 static_assert()
强制 ForwardToFoo::operator()<>
在没有参数的情况下实例化,因此触发编译错误。
那么有没有办法把这个实例化错误变成SFINAE错误,可以在没有编译错误的情况下处理?
这个问题与转发与否无关。在上一个 static_assert
中,您请求编译器在没有参数的情况下实例化 ForwardToFoo::operator()
。在该运算符中,您调用 Foo::operator()
,它只有一个采用 int 的重载。这显然会导致硬编译器错误。
std::is_invocable
在其他情况下有效,因为它实际上并未实例化不存在的运算符。
您的代码的一个潜在修复方法是强制将至少一个参数传递给 ForwardToFoo::operator()
:
struct ForwardToFoo {
template<class Arg0, class ...Args>
constexpr decltype(auto) operator()( Arg0 arg0, Args ...args ) const {
Foo foo;
return foo(arg0, args...);
}
};
然后,编译器将不会实例化该运算符(因为它不能在没有参数的情况下调用)。
或者您可以使用其他答案中所示的 expression-sfinae。
您得到与从
得到的相同的错误
ForwardToFoo{}();
您知道 ForwardToFoo
中的 operator()
可以不带参数调用。但是当它调用 Foo()
中的运算符时,没有参数......你会得到错误。
Is there a way to work around it?
是的:只有当 Foo()::operator()
可以使用参数调用时,SFINAE 才能启用 ForwardToFoo()::operator()
。
我的意思是...你可以这样写ForwardToFoo()::operator()
template<class ...Args>
constexpr auto operator()( Args &&...args ) const
-> decltype( std::declval<Foo>()(std::forward<Args>(args)...) )
{ return Foo{}( std::forward<Args>( args )... ); }
-- 编辑--
Jeff Garret 指出了我遗漏的重要一点。
一般来说,std::invokable
的简单使用不会导致第一个参数中可调用对象的实例化。
但在这种特殊情况下,ForwardToFoo::operator()
的 return 类型是 decltype(auto)
。这会强制编译器检测 returned 类型,这会导致实例化和错误。
反例:如果您将运算符编写为调用 Foo{}()
的 void
函数,则转发参数但不 returning 值,
template <typename ... Args>
constexpr void operator() ( Args && ... args ) const
{ Foo{}( std::forward<Args>( args )... ); }
现在编译器知道 returned 类型是 void
而无需实例化它。
你也从
得到一个编译错误
static_assert( std::is_invocable_v<ForwardToFoo> == false );
但这次是因为 ForwardToFoo{}()
结果可调用而无需参数。
如果你写
static_assert( std::is_invocable_v<ForwardToFoo> == true );
错误消失。
保持真实
ForwardToFoo{}();
给出编译错误,因为这会实例化运算符。
我不太明白为什么你希望它起作用。
Foo
需要 int
才能调用,因此 ForwardToFoo
也是如此。否则它对 Foo
的调用将是错误的。
无论您是转发论点还是复制论点或其他任何东西,都没有关系:它们仍然必须提供。
想想你将如何调用 ForwardWithFoo
。你能不争辩地做到吗?会发生什么?
我有一个 class,它只是将函数调用转发给另一个 class,我希望能够在我的转发 class 上使用 std::invocable<>
。但由于某种原因失败了……这是我应该期待的吗?有解决办法吗?
#include <type_traits>
#include <utility>
struct Foo {
constexpr int operator()( int i ) const {
return i;
}
};
struct ForwardToFoo {
template<class ...Args>
constexpr decltype(auto) operator()( Args &&...args ) const {
Foo foo;
return foo( std::forward<Args>( args )... );
}
};
int main( void ) {
// These work fine
static_assert( std::is_invocable_v<ForwardToFoo, int> == true );
static_assert( std::is_invocable_v<Foo, int> == true );
static_assert( std::is_invocable_v<Foo> == false );
// This causes a compile error
static_assert( std::is_invocable_v<ForwardToFoo> == false );
return 0;
}
编辑:
到目前为止的答案表明问题是最后一个 static_assert()
强制 ForwardToFoo::operator()<>
在没有参数的情况下实例化,因此触发编译错误。
那么有没有办法把这个实例化错误变成SFINAE错误,可以在没有编译错误的情况下处理?
这个问题与转发与否无关。在上一个 static_assert
中,您请求编译器在没有参数的情况下实例化 ForwardToFoo::operator()
。在该运算符中,您调用 Foo::operator()
,它只有一个采用 int 的重载。这显然会导致硬编译器错误。
std::is_invocable
在其他情况下有效,因为它实际上并未实例化不存在的运算符。
您的代码的一个潜在修复方法是强制将至少一个参数传递给 ForwardToFoo::operator()
:
struct ForwardToFoo {
template<class Arg0, class ...Args>
constexpr decltype(auto) operator()( Arg0 arg0, Args ...args ) const {
Foo foo;
return foo(arg0, args...);
}
};
然后,编译器将不会实例化该运算符(因为它不能在没有参数的情况下调用)。
或者您可以使用其他答案中所示的 expression-sfinae。
您得到与从
得到的相同的错误ForwardToFoo{}();
您知道 ForwardToFoo
中的 operator()
可以不带参数调用。但是当它调用 Foo()
中的运算符时,没有参数......你会得到错误。
Is there a way to work around it?
是的:只有当 Foo()::operator()
可以使用参数调用时,SFINAE 才能启用 ForwardToFoo()::operator()
。
我的意思是...你可以这样写ForwardToFoo()::operator()
template<class ...Args>
constexpr auto operator()( Args &&...args ) const
-> decltype( std::declval<Foo>()(std::forward<Args>(args)...) )
{ return Foo{}( std::forward<Args>( args )... ); }
-- 编辑--
Jeff Garret 指出了我遗漏的重要一点。
一般来说,std::invokable
的简单使用不会导致第一个参数中可调用对象的实例化。
但在这种特殊情况下,ForwardToFoo::operator()
的 return 类型是 decltype(auto)
。这会强制编译器检测 returned 类型,这会导致实例化和错误。
反例:如果您将运算符编写为调用 Foo{}()
的 void
函数,则转发参数但不 returning 值,
template <typename ... Args>
constexpr void operator() ( Args && ... args ) const
{ Foo{}( std::forward<Args>( args )... ); }
现在编译器知道 returned 类型是 void
而无需实例化它。
你也从
得到一个编译错误static_assert( std::is_invocable_v<ForwardToFoo> == false );
但这次是因为 ForwardToFoo{}()
结果可调用而无需参数。
如果你写
static_assert( std::is_invocable_v<ForwardToFoo> == true );
错误消失。
保持真实
ForwardToFoo{}();
给出编译错误,因为这会实例化运算符。
我不太明白为什么你希望它起作用。
Foo
需要 int
才能调用,因此 ForwardToFoo
也是如此。否则它对 Foo
的调用将是错误的。
无论您是转发论点还是复制论点或其他任何东西,都没有关系:它们仍然必须提供。
想想你将如何调用 ForwardWithFoo
。你能不争辩地做到吗?会发生什么?