10

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?

Georg P.
  • 2,785
  • 2
  • 27
  • 53
  • 1
    Looks like it's still trying to do deduction for `Args` (i.e., you specify the first and it figures out the rest). – chris Sep 26 '17 at 15:36
  • `make_unique` is from C++14, so you're probably using Visual Studio 2013 – AndyG Sep 26 '17 at 15:45
  • @RustyX both latest gcc and clang have troubles with it – AMA Sep 26 '17 at 15:45
  • I noticed it starts compiling in Clang when I force non-deduction for `Args` in the `std::function` instantiation. – chris Sep 26 '17 at 15:45
  • Hmm. It looks like both gcc and clang think that you didn't specify all of the template arguments for `MakeFoo`. You have given it an `int`, but there's still the rest of the pack to be deduced. And it's non-deducible. (I know you want it to be empty). – n. m. could be an AI Sep 26 '17 at 15:53
  • Here's a weird workaround: `template T id(T t){return t;}` Now `id(MakeFoo)(&Bar::MemberFunction)` works. – n. m. could be an AI Sep 26 '17 at 16:27

3 Answers3

5

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.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

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);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • This looks like a nice and simple solution, but unfortunately my actual code is a bit more complicated. I also need to be generic about the type of object, on which the member function is called. So the template looks more like `template`, where `Obj` can be set to `Bar` or any other class. In this case, I can't really write the argument of `MakeFoo` like this, because it depends on `Bar`. – Georg P. Sep 27 '17 at 08:42
1
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 ) {
Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22