I want to pass a callable (std::function
object) into a class Foo
. The callable refers to a member method of another class which has arbitrary arguments, hence the Foo
must be a variadic template. Consider this code:
struct Bar {
void MemberFunction(int x) {}
};
template<typename ...Args>
class Foo {
public:
Foo(std::function<void(Bar*, Args...)> f) {}
};
int main() {
Foo<int> m1(&Bar::MemberFunction);
return 0;
}
This compiles fine. Now I want to write a factory function MakeFoo()
which returns a unique_ptr
to a Foo
object:
template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
return std::make_unique<Foo<Args...>>(f);
}
Using this function by calling
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
in main, gives me the following compiler errors:
functional.cc: In function ‘int main()’:
functional.cc:21:50: error: no matching function for call to ‘MakeFoo(void (Bar::*)(int))’
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
^
functional.cc:15:35: note: candidate: template<class ... Args> std::unique_ptr<Foo<Args ...> > MakeFoo(std::function<void(Bar*, Args ...)>)
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
^
functional.cc:15:35: note: template argument deduction/substitution failed:
functional.cc:21:50: note: mismatched types ‘std::function<void(Bar*, Args ...)>’ and ‘void (Bar::*)(int)’
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
It seems to me, that when I call the constructor of Foo
, the compiler happily converts the function pointer &Bar::MemberFunction
to a std::function
object. But when I pass the same argument to the factory function, it complains. Moreover, this problem only seems to occur, when Foo
and MakeFoo
are variadic templates. For a fixed number of template parameters it works fine.
Can somebody explain this to me?
<int>
?Prior to C++17, template type deduction is pure pattern matching.
std::function<void(Foo*)>
can store a member function pointer of type void(Foo::*)()
, but a void(Foo::*)()
is not a std::function
of any kind.
MakeFoo
takes its argument, and pattern matches std::function<void(Bar*, Args...)>
. As its argument is not a std::function
, this pattern matching fails.
In your other case, you had fixed Args...
, and all it had to do was convert to a std::function<void(Bar*, Args...)>
. And there is no problem.
What can be converted to is different than what can be deduced. There are a myriad of types of std::function
a given member function could be converted to. For example:
struct Foo {
void set( double );
};
std::function< void(Foo*, int) > hello = &Foo::set;
std::function< void(Foo*, double) > or_this = &Foo::set;
std::function< void(Foo*, char) > why_not_this = &Foo::set;
In this case there is ambiguity; in the general case, the set of template arguments that could be used to construct some arbitrary template type from an argument requires inverting a turing-complete computation, which involves solving Halt.
Now, C++17 added deduction guides. They permit:
std::function f = &Foo::set;
and f
deduces the signature for you.
In C++17, deduction doesn't guides don't kick in here; they may elsewhere, or later on.
<int>
?Because it still tries to pattern match and determine what the rest of Args...
are.
If you changed MakeFoo
to
template<class T>
std::unique_ptr<Foo<T>> MakeFoo(std::function<void(Bar*, T)> f) {
return std::make_unique<Foo<T>>(f);
}
suddenly your code compiles. You pass it int
, there is no deduction to do, and you win.
But when you have
template<class...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
return std::make_unique<Foo<T>>(f);
}
the compiler sees <int>
and says "ok, so Args...
starts with int
. What comes next?".
And it tries to pattern match.
And it fails.
template<class T>struct tag_t{using type=T; constexpr tag_t(){}};
template<class T>using block_deduction=typename tag_t<T>::type;
template<class...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(
block_deduction<std::function<void(Bar*, Args...)>> f
) {
return std::make_unique<Foo<T>>(f);
}
now I have told the compiler not to deduce using the first argument.
With nothing to deduce, it is satisfied that Args...
is just int
, and... it now works.
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