I have a class that is simply forwarding the function call to another class and I would like to be able to use std::invocable<> on my forwarding class. But for some reason that fails... Is this what I should expect? Is there a way to work around it?
#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;
}
Edit:
The answers so far suggest that the issue is that the last static_assert() forces ForwardToFoo::operator()<> to be instantiated without arguments hence triggering a compile error.
So is there a way to turn this instantiation error into a SFINAE error that can be handled without a compile error?
You get the same error that you get from
ForwardToFoo{}();
you have that the operator() in ForwardToFoo is invocable without arguments. But when it call the operator in Foo(), without arguments... you get the error.
Is there a way to work around it?
Yes: you can SFINAE enable ForwardToFoo()::operator() only when Foo()::operator() is callable with the arguments.
I mean... you can write ForwardToFoo()::operator() as follows
template<class ...Args>
constexpr auto operator()( Args &&...args ) const
-> decltype( std::declval<Foo>()(std::forward<Args>(args)...) )
{ return Foo{}( std::forward<Args>( args )... ); }
-- EDIT --
Jeff Garret notes an important point that I missed.
Generally speaking, the simple use of std::invokable doesn't cause the instantiation of the callable in first argument.
But in this particular case the return type of ForwardToFoo::operator() is decltype(auto). This force the compiler to detect the returned type and this bring to the instantiation and the error.
Counterexample: if you write the operator as a void function that call Foo{}(), forwarding the arguments but not returning the value,
template <typename ... Args>
constexpr void operator() ( Args && ... args ) const
{ Foo{}( std::forward<Args>( args )... ); }
now the compiler know that the returned type is void without instantiating it.
You also get a compilation error from
static_assert( std::is_invocable_v<ForwardToFoo> == false );
but this time is because ForwardToFoo{}() result invocable without arguments.
If you write
static_assert( std::is_invocable_v<ForwardToFoo> == true );
the error disappear.
Remain true that
ForwardToFoo{}();
gives a compilation error because this instantiate the operator.
I can't quite work out why you expected this to work.
Foo requires an int to be callable, so ForwardToFoo does too. Otherwise its call to Foo will be ill-formed.
It doesn't really matter whether you're forwarding the arguments or copying them or anything else: they still have to be provided.
Think about how you would invoke ForwardWithFoo. Could you do it without arguments? What would happen?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With