为什么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。

live example

您得到与从

得到的相同的错误
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。你能不争辩地做到吗?会发生什么?