5

Consider the following code:

#include <iostream>

struct S {
  void f(const char* s) {
    std::cout << s << '\n';
  }
};

template <typename... Args, void(S::*mem_fn)(Args...)>
void invoke(S* pd, Args... args) {
  (pd->*mem_fn)(args...);
}

int main() {
  S s;
  void(*pfn)(S*, const char*) = invoke<const char*, &S::f>;
  pfn(&s, "hello");
}

When compiling the code, clang gives the following error:

main.cpp:16:33: error: address of overloaded function 'invoke' does not match required type 'void (S *, const char *)'
  void(*pfn)(S*, const char*) = invoke<const char*, &S::f>
                                ^~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:10:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Args'
void invoke(S* pd, Args... args) {
     ^
1 error generated.

The message seems to suggest that the template instantiation invoke<const char*, &S::f> has failed. Could somebody give me some clues as to why is this? I believe it has something to do with the parameter pack.

Lingxi
  • 14,579
  • 2
  • 37
  • 93

2 Answers2

6

Your code is ill-formed. mem_fn is in a non-deduced context according to [temp.deduct.type]:

The non-deduced contexts are:
— ...
— A function parameter pack that does not occur at the end of the parameter-declaration-list.

And from [temp.param]:

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument (14.8.2). [ Example:

template<class T1 = int, class T2> class B; // error

// U can be neither deduced from the parameter-type-list nor specified
template<class... T, class... U> void f() { } // error
template<class... T, class U> void g() { } // error

—end example ]

The mem_fn argument in this declaration:

template <typename... Args, void(S::*mem_fn)(Args...)>
void invoke(S* pd, Args... args) {

follows a template parameter pack. It cannot be deduced from the list. You could, however, pass it as an argument:

template <typename... Args>
void invoke(S* pd, void(S::*mem_fn)(Args...), Args... args);

Or wrap the whole thing in a struct so that you don't need to follow a parameter pack with another template:

template <typename... Args>
struct Invoke {
    template <void (S::*mem_fn)(Args...)>
    static void invoke(S* pd, Args... args);
};

void(*pfn)(S*, const char*) = Invoke<const char*>::invoke<&S::f>;
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Why it cannot be deduced given that `Args` has been explicitly specified? – Lingxi Apr 29 '15 at 11:08
  • OK. I seem to get it. The value of `mem_fn` is explicitly specified rather than deduced from `Args` that precedes it. – Lingxi Apr 29 '15 at 11:10
  • @Lingxi It cannot be deduced quite simply because it's a non-deduced context. The standard simply takes a conservative approach to handling parameter packs. – Barry Apr 29 '15 at 11:16
2

A slightly more generic alternative to Barry's solution could use decltype to deduce the entire member function signature, including the class it belongs to:

template <typename Sig, Sig Fn>
struct invoke;

template <typename Ret, class Class, typename... Args, Ret(Class::*mem_fn)(Args...)>
struct invoke <Ret(Class::*)(Args...), mem_fn>
{
    static void exec (Class* pd, Args... args)
    {
        (pd->*mem_fn)(args...);
    }
};

Then use it like this:

void(*pfn)(S*, const char*) = invoke<decltype(&S::f),&S::f>::exec;
TartanLlama
  • 63,752
  • 13
  • 157
  • 193