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应用程序

下一篇: C ++将std :: function对象传递给variadic模板