Search code examples
c++variadic-templates

Pointer to member variadic method as a template parameter


Using C++17, I need to modify the struct Bind below to handle methods with arbitrary parameters. The methods' return values will always be void. The goal is to create function pointers in a constexpr context that can be used to create std::functions later in a non-constexpr context.

https://godbolt.org/z/q4M1KxqPh

#include <functional>

template <typename Owner, typename Owned, Owned Owner::* Member, void(Owned::* Method)(int)>
struct Bind {
    static auto apply(Owner* owner) {
        return std::function<void(int)>( [=](int x) { ((owner->*Member).*Method)(x); } ); 
    }
};

struct Foo {
    void x(int x) { }
    void y(float y, int x) { }
};

struct Bar {
    Foo foo1;
    Foo foo2;
};

int main() {
    Bar bar;

    std::function<void(int)> (*bindX)(Bar* owner) = &Bind<Bar, Foo, &Bar::foo1, &Foo::x>::apply;
    std::function<void(int)> targetX = bindX(&bar);
    targetX(42);


    std::function<void(float, int)> (*bindY)(Bar* owner) = &Bind<Bar, Foo, &Bar::foo2, &Foo::y>::apply;
    std::function<void(float, int)> targetY = bindY(&bar);
    targetY(3.14f, 42);
}

error: could not convert template argument '&Foo::y' from 'void (Foo::*)(float, int)' to 'void (Foo::*)(int)'

The above demonstrates two example argument lists. But, Bind needs to work for function signatures with arbitrary arguments fixed at compile time.

This problem looks like it needs something like

template <typename Owner, typename Owned, Owned Owner::* Member, typename ...Args, void(Owned::* Method)(Args...)>
struct Bind {
    static auto apply(Owner* owner) {
        return std::function<void(int)>( [=](Args&&... args) { 
            ((owner->*Member).*Method)(std::forward<Args>(args)...); 
        }); 
    }
};

But, that won't work because error: parameter pack 'Args' must be at the end of the template parameter list

Or, maybe

template <typename Owner, typename Owned, Owned Owner::* Member, template<typename ...Args> void(Owned::* Method)(Args...)>
struct Bind {
    static auto apply(Owner* owner) {
        return std::function<void(int)>( [=](Args&&... args) { 
            ((owner->*Member).*Method)(std::forward<Args>(args)...); 
        }); 
    }
};

But, that does not parse either. expected 'class' or 'typename' before 'void'

Is there such a thing as a "templated template non-type parameter"?

This also looks like it could be a good use for template <auto Method>. But, I don't know how to make the signature of the lambda work...


Solution

  • Inspired by https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0127r1.html

    I managed to solve this with template partial specialization. https://godbolt.org/z/7doP5563e

    #include <functional>
    
    template <typename Owner, typename Owned, Owned Owner::* Member, typename Method, Method method>
    struct Bind;
    
    template <typename Owner, typename Owned, Owned Owner::* Member, typename... Args, void(Owned::* method)(Args...)>
    struct Bind<Owner, Owned, Member, void(Owned::*)(Args...), method> {
        static auto apply(Owner* owner) {
            return std::function<void(Args...)>( [=](Args&&... args) { 
                ((owner->*Member).*method)(std::forward<Args>(args)...); 
            }); 
        }
    };
    
    struct Foo {
        void x(int x) { }
        void y(float y, int x) { }
    };
    
    struct Bar {
        Foo foo1;
        Foo foo2;
    };
    
    int main() {
        Bar bar;
    
        std::function<void(int)> (*bindX)(Bar* owner) = &Bind<Bar, Foo, &Bar::foo1, decltype(&Foo::x), &Foo::x>::apply;
        std::function<void(int)> targetX = bindX(&bar);
        targetX(42);
    
    
        std::function<void(float, int)> (*bindY)(Bar* owner) = &Bind<Bar, Foo, &Bar::foo2, decltype(&Foo::y), &Foo::y>::apply;
        std::function<void(float, int)> targetY = bindY(&bar);
        targetY(3.14f, 42);
    }