C++ Passing std::function object to variadic template
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?
Why doesn't it work without explicit <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.
Why doesn't it work with explicit <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.
How can you fix it?
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.
The compiler cannot deduce the template arguments for std::function
from a different type, such as member function pointer. Even though a std::function
can be constructed from on object of that type, to consider the constructor the template arguments of std::function
must be first known.
To help it deduce, add another overload:
template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(void(Bar::*f)(Args...)) {
return std::make_unique<Foo<Args...>>(f);
}
MakeFoo<int>( whatever );
is equivalent to invoking the hypothetical
template<typename ...Tail>
std::unique_ptr<Foo<int,Tail...>> MakeFoo( std::function<void(Bar*,int,Tail...)> f) {
return std::make_unique<Foo<int,Tail...>>(f);
}
clearly, in no way the compiler can deduce that Tail is empty given a void(Bar::*)(int)
IMO, the most correct fix ( given the required usage ) is to make the args non deduced:
template< typename T >
struct nondeduced { using type = T; };
template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo( std::function<void(Bar*, typename nondeduced<Args>::type... )> f ) {
链接地址: http://www.djcxy.com/p/66900.html
上一篇: 安装没有SD卡的Android应用程序