c++lambda

templated pointer to member function with lambda as argument


In Why member functions can't be used as template arguments?, I see that you can use pointers to member functions as template parameters, for example:

struct Foo {
    void Bar() { // do something
    }
};

template <typename TOwner, void(TOwner::*func)()>
void Call(TOwner *p) {
    (p->*func)();
}

int main() {
    Foo a;
    Call<Foo, &Foo::Bar>(&a);
    return 0;
} 

However, how would it have to be adapted if Foo::Bar would take a lambda as an argument? What would I have to replace ??? with in the following code?

template<typename T>
concept has_call_operator = requires { &T::operator(); };

struct Foo {
    void Bar(has_call_operator auto lambda) {}
};

template <typename TOwner, void(TOwner::*func)(???)>
void CallFoo(TOwner *p) {
    (p->*func)([](){});
}

Solution

  • The issue here is that void Bar(has_call_operator auto lambda) is a template. You cannot take its address before instantiating it. You only instantiate it when called, because you want the template argument to be deduced from the parameter. You can defer instantiation by wrapping it in a lambda that can be used as auto template argument:

    #include <functional>
    
    template<typename T>
    concept has_call_operator = requires { &T::operator(); };
    
    struct Foo {
    
        void Bar(has_call_operator auto lambda) {}
    };
    
    
    template <typename TOwner, auto func>
    void CallFoo(TOwner *p) {
        std::invoke(func, p, [](){});
    }
    
    int main() {
        Foo a;
        CallFoo<Foo, [](Foo* f, auto g){ f->Bar(g); }>(&a);
        return 0;
    } 
    

    Live Demo

    [](Foo* f, auto g){ f->Bar(g); } has a templated operator(), but the type of the lambda itself can be deduced by the compiler as the type of the func template argument. The operator() of that lambda gets instantiated when called (via std::invoke()) and in turn instantiates Foo::Bar.


    If you want Bar to accept only lambdas without capture and a call operator with signature size_t(size_t) then not much has to be changed on your initial code, because such lambdas can be converted to a function pointer:

    #include <functional>
    
    using Func_t = size_t(*)(size_t);
    
    struct Foo {    
        void Bar(Func_t lambda) {}
    };
    
    using Bar_t = void(Foo::*)(Func_t);
    
    template <typename TOwner, Bar_t func>
    void CallFoo(TOwner *p) {
        std::invoke(func, p, [](size_t i){ return size_t{42}; });
    }
    
    int main() {
        Foo a;
        CallFoo<Foo, &Foo::Bar>(&a);
        return 0;
    } 
    

    Live Demo