Search code examples
c++templatessfinaepointer-to-member

Template syntax to substitute only member functions


I am tried to create a function overload so it only binds (works for) a member function. I took a look at the function signature of std::mem_fn http://en.cppreference.com/w/cpp/utility/functional/mem_fn

template <class Ret, class T>
/* unspecified */ mem_fn (Ret T::* pm);

So I structured my parameters as such

template <typename R, typename F>
auto call_me(R C::* func) {
    return (mContainer.*func);
}

However, I then get this error

.\template.cpp: In function 'int main()':
.\template.cpp:29:49: error: no matching function for call to 'MyClass<int, std::vector<int> >::call_me(std::vector<int>::size_type (std::vector<int>::*)() const noexcept)'
     cout << test.call_me(&std::vector<int>::size) << endl;
                                                 ^
.\template.cpp:16:10: note: candidate: template<class R, class F> auto MyClass<T, C>::call_me(R C::*) [with R = R; F = F; T = int; C = std::vector<int>]
     auto call_me(R C::* func) {
          ^~~~~~~
.\template.cpp:16:10: note:   template argument deduction/substitution failed:
.\template.cpp:29:49: note:   couldn't deduce template parameter 'F'
     cout << test.call_me(&std::vector<int>::size) << endl;

The reason I am trying to do this is so I can have an overload that works for general lambda and functional objects and another overload that works for member functional pointers. Here is an minimal example of what I am trying to achieve. I know this question is a bit confusing so please feel free to ask for clarification if need be.

#include <vector>
#include <iostream>

using namespace std;

template <typename T, typename C>
struct MyClass {
    // This doesnt work because of SFINAE

    template <typename F, typename... A>
    auto call_me(F func, A... args) { // lambda version
        return func(args...);
    }

    template <typename R, typename F>
    auto call_me(R C::* func) { // member function version
        return (mContainer.*func);
    }

    C mContainer; // this is private in my actual code

};


int main() {
    MyClass<int, std::vector<int> > test;;

    // these two calls will call the member function version of the overload
    cout << test.call_me(&std::vector<int>::size) << endl;

    using insert_func_t = std::vector<int>::iterator(std::vector<int>::*)(std::vector<int>::const_iterator, const int&);
    test.call_me(static_cast<insert_func_t>(&std::vector<int>::insert), test.mContainer.begin(), 4);

    // this call will call the lambda version of the overload
    cout << test.call_me([](std::vector<int>& in){ in.push_back(5); });

    return 0;
}

Solution

  • You can cover both cases with std::invoke:

    template <typename F, typename... A>
    auto call_me(F func, A... args) { // lambda version
        return std::invoke(func, mContainer, args...);
    }
    

    For a function object, like your closure, this calls operator(). For a member function, it prefixes the arguments with the object to use and then calls it with appropriate syntax. In other words, your work is already done.

    You could also take perfect forwarding into account:

    template <typename F, typename... A>
    auto call_me(F&& func, A&&... args) { // lambda version
        return std::invoke(std::forward<F>(func), mContainer, std::forward<A>(args)...);
    }