Search code examples
c++templatesvariadic-templatespointer-to-membertemplate-argument-deduction

Error calling templated pointer-to-member function with a return type


template<typename T, typename F, typename ...Args>
auto f(F h, Args&&... args) -> decltype(h(args...)) {
    T* t = new T(); // Don't worry, my actual code doesn't do this
    return (t->h)(args...);
}

struct G {
    int g() {
        return 5;
    }
};

int main() {
    int k = f<G>(&G::g);
}

Microsoft's compiler says error C2672: 'f': no matching overloaded function found and error C2893: Failed to specialize function template 'unknown-type f(F,Args &&...)'.

Clang's compiler says note: candidate template ignored: substitution failure [with T = G, F = int (G::*)(), Args = <>]: called object type 'int (G::*)()' is not a function or function pointer and error: no matching function for call to 'f'.

I'm pretty sure int (G::*)() is a function pointer ... ? What am I missing? (All of this worked fine before I added the return type.)


Solution

  • I'm pretty sure int (G::*)() is a function pointer ... ? What am I missing?

    Non exactly: int (G::*)() is a pointer to a non-static method. That isn't exactly the same things and require a little different syntax to call it.

    So, instead of

    return (t->h)(args...);
    

    you should add a * and call h() as follows

    return (t->*h)(args...);
    // ........^  add this *
    

    The decltype() is also wrong. If you can use at least C++14, you can avoid it and simply use auto as return type

    template <typename T, typename F, typename ...Args>
    auto f (F h, Args&&... args) {
        T* t = new T(); 
        return (t->*h)(args...);
    }
    

    otherwise, if you must use C++11, you can include <utility> and use std::declval() as follows

    template <typename T, typename F, typename ...Args>
    auto f(F h, Args&&... args) -> decltype((std::declval<T*>()->*h)(args...)) { 
        T* t = new T(); // .................^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        return (t->*h)(args...);
    }
    

    But there is another way to write your f() function: deducing the returned type (so avoiding the auto, the decltype() and the std::declval()) and the arguments of h()

    You can write f() as follows

    template<typename R, typename T, typename ... As1, typename ... As2>
    R f(R(T::*h)(As1...), As2 && ... args) {
        T* t = new T();
        return (t->*h)(args...);
    }
    

    and you avoid to explicit the G type calling it

    int k = f(&G::g);
    // .....^^^^^^^^   no more explicit <G> needed
    

    because the T template type is deduced from the argument &G::g.