为什么 std::is_invocable 不能处理转发?

iol*_*olo 7 c++ perfect-forwarding c++17

我有一个类只是将函数调用转发到另一个类,我希望能够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;
}
Run Code Online (Sandbox Code Playgroud)

编辑: 到目前为止的答案表明问题是最后一个static_assert()强制ForwardToFoo::operator()<>实例化而不带参数因此触发编译错误。那么有没有办法将这个实例化错误变成可以在没有编译错误的情况下处理的 SFINAE 错误?

max*_*x66 6

你会得到和你一样的错误

ForwardToFoo{}();
Run Code Online (Sandbox Code Playgroud)

你知道operator()inForwardToFoo是无需参数即可调用的。但是当它在Foo()不带参数的情况下调用运算符时......你会得到错误。

有没有办法解决它?

是的:ForwardToFoo()::operator()只有在Foo()::operator()可以使用参数调用时才能启用 SFINAE 。

我的意思是......你可以写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 )... ); }
Run Code Online (Sandbox Code Playgroud)

- 编辑 -

Jeff Garret 指出了我遗漏的一个重要观点。

一般来说,简单的使用std::invokable不会导致第一个参数中可调用的实例化。

但在这种特殊情况下,的返回类型ForwardToFoo::operator()decltype(auto)。这迫使编译器检测返回的类型,这会导致实例化和错误。

反例:如果您将运算符编写为void调用的函数Foo{}(),则转发参数但不返回值,

template <typename ... Args>
constexpr void operator() ( Args && ... args ) const
 { Foo{}( std::forward<Args>( args )... ); }
Run Code Online (Sandbox Code Playgroud)

现在编译器知道返回的类型void没有实例化它。

您还会从以下位置收到编译错误

static_assert( std::is_invocable_v<ForwardToFoo> == false );
Run Code Online (Sandbox Code Playgroud)

但这一次是因为ForwardToFoo{}()结果可以无参数调用。

如果你写

static_assert( std::is_invocable_v<ForwardToFoo> == true );
Run Code Online (Sandbox Code Playgroud)

错误消失。

保持真实

ForwardToFoo{}();
Run Code Online (Sandbox Code Playgroud)

给出编译错误,因为这会实例化运算符。

  • 我没有看到任何人提到,但它是相关的,并且在某种程度上转发确实发挥了作用:is_invocable 本身不会导致实例化;它只使用声明。但该函数使用 decltype(auto) 返回类型,因此它是触发实例化的*那个*。如果返回类型已知(例如 int),则不会实例化主体,并且 is_invocable 将返回 true。(is_invocable 不回答你可以调用它而没有错误。它回答声明是否说你可以调用它。) (5认同)