Search code examples
lambdac++17variadic-templatespointer-to-membergeneric-lambda

Perfect forwarding of lambda arguments to member function, where member function is a non-type template parameter


Context

I want to wrap a member function and a specific object into a function object (which I'll use as a callback later). I would like to write this wrapping function once for different member functions and objects especially because my actual lambda does some extra work before calling the wrapped method. Here are a few possible implementations:

#include <iostream>
#include <string>
#include <utility>

template <class ClassT, class... ArgsT>
auto getCallbackPtr(ClassT* obj, void(ClassT::* memfn)(ArgsT...))
{
    return [obj, memfn](ArgsT&&... args) {
        (obj->*memfn)(std::forward<ArgsT>(args)...);
    };
}
template <auto memFn, class ClassT>
auto getCallbackTemplate(ClassT* obj)
{
    return [obj](auto&&... args){
        return (obj->*memFn)(std::forward<decltype(args)>(args)...);
    };
}
template <auto memFn, class ClassT, class... ArgsT>
auto getCallbackRedundant(ClassT* obj)
{
    return [obj](ArgsT&&... args){
        return (obj->*memFn)(std::forward<ArgsT&&>(args)...);
    };
}

// Example of use
class Foo {
public:
    void bar(size_t& x, const std::string& s) { x=s.size(); }
};
int main() {
    Foo f; 
    auto c1 = getCallbackPtr(&f, &Foo::bar);
    size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
    auto c2 = getCallbackTemplate<&Foo::bar>(&f);
    size_t x2; c2(x2, "123"); std::cout << "c2:" << x2 << "\n";
    auto c3 = getCallbackRedundant<&Foo::bar, Foo, size_t&, const std::string&>(&f);
    size_t x3; c3(x3, "123"); std::cout << "c3:" << x3 << "\n";
}

Question (in short)

I would like a function that combines different aspects of the above three functions:

  • It should takes the member function as a compile-time template parameter, unlike getCallbackPtr().
  • Its operator() should not be a templated function, unlike getCallbackTemplate().
  • Its template parameters (except the member function pointer) should inferred from the function use, unlike getCallbackRedundant().

Some details

Here are my reasons for wanting the member function to be a template parameter, although I must admit these probably won't have a noticable effect in practice:

  • The optimizer will probably make the call to member function directly rather than via a function pointer. In fact, as this is the only place the member function is called from, it might even be inlined into the lambda by the compiler.
  • The resulting function object is smaller (one pointer rather than one pointer plus one member function pointer), and so is more likely to fit into the footprint of a std::function (small object optimization).

Here are the problems with the getCallbackTemplate(), which has a templated operator():

  • It doesn't work with Visual Studio. This is a show stopper for me. (The error is error C3533: a parameter cannot have a type that contains 'auto', in reference to template <auto memFn, class ClassT>.)
  • If the wrong types of arguments are passed in, I suspect it will have a more complicated and confusing compiler error than a non-templated operator() (admittedly this is just a hunch).
  • The templated operator() is not capable of accepting initializer lists for arguments. This is not an issue at all for me but I mention it for the record.

I think the reasons for wanting inferred template parameters are fairly clear: getCallbackRedundant() is distractingly verbose and harder to use.

Can this be done? How?


Solution

  • One easy way to deduce the arguments is by using partial template specialization.

    In this example I'm solving the problem by forwarding the non-type member function pointer and it's type to a custom functor, that is then returned.

    Partially specialize on the type and from there the rest is straight-forward.

    #include <iostream>
    #include <string>
    
    template <auto memFnPtr, class memFn>
    struct getCallbackTemplate;
    
    template <auto memFnPtr, class Ret, class ClassT, class... Args>
    struct getCallbackTemplate<memFnPtr, Ret(ClassT::*)(Args...)>
    {
        getCallbackTemplate (ClassT* obj) : m_obj(obj) {}
    
        Ret operator()(Args... args) {
            return (m_obj->*memFnPtr)(std::forward<Args>(args)...);
        }
    
        ClassT* m_obj;
    };
    
    template <auto memFn, class ClassT>
    auto getCallback(ClassT* obj) {
        return getCallbackTemplate<memFn, decltype(memFn)>(obj);
    }
    
    class Foo {
    public:
        void bar(std::size_t& x, const std::string& s) { x=s.size(); }
    };
    
    int main() {
        Foo f; 
        auto c1 = getCallback<&Foo::bar>(&f);
        size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
    }