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