3

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?

max66
  • 65,235
  • 10
  • 71
  • 111
Arthur Tacca
  • 8,833
  • 2
  • 31
  • 49
  • 3
    *I want to wrap a member function and a specific object into a function object* - are you trying to re-invent `std::function`? – SergeyA Jun 06 '18 at 15:55
  • @SergeyA A `std::function` can be constructed from a member function, in which case the first parameter must be an appropriate object. But you cannot directly construct a `std::function` from a member function \*and an object\*. – Arthur Tacca Jun 06 '18 at 16:02
  • 1
    @ArthurTacca Sure you can, just pass a lambda into `std::function` constructor. And lambda is smaller and more efficient then calling through a member function pointer. – Maxim Egorushkin Jun 06 '18 at 16:05
  • 1
    Arthur, you use it either with capturing lamda or with `std::bind` (the later being really old school) – SergeyA Jun 06 '18 at 16:06
  • 1
    *"It should takes the member function as a compile-time template parameter, unlike getCallbackPtr."* - You won't be able to have the member function pointer as a single template parameter unless you can use `auto`, which apparently VC doesn't support. – Holt Jun 06 '18 at 16:09
  • @MaximEgorushkin Yes, you would use a capturing lambda. That is exactly what I am doing in all three functions in my question! I am asking about a how to use capturing lambdas neatly in conjunction with a couple of other features. Once I have done that, I could change my return type from `auto` to `std::function<...>`; that is really the easy bit. – Arthur Tacca Jun 06 '18 at 16:18
  • @SergeyA As I just said to Maxim, I am already using capturing lambdas, that is what the three functions in my questions are wrapping. Passing that to `std::function` is the easy bit at the end. – Arthur Tacca Jun 06 '18 at 16:19
  • @Holt Thanks for the only constructive comment so far! I suspected that it might just be impossible. VS does accept member function pointers as non-type template parameters, even when the type of the class and parameters are template parameters, so long as those type parameters are before the member function parameter. But they would need to go after the memfn parameter to have a hope of being inferred and to be variadic – a chicken and egg problem. But I was hoping there was something I just don't know about. – Arthur Tacca Jun 06 '18 at 16:34
  • @Holt Also, I am still interested in a solution that uses `auto` template parameter for the member function pointer, but avoids the `auto` lambda. I presume some sort of `ArgTypes` traits class would be needed but I don't know how this would work with variadic template parameters. – Arthur Tacca Jun 06 '18 at 16:36

3 Answers3

2

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";
}
super
  • 12,335
  • 2
  • 19
  • 29
  • You need to `forward` in `operator()`, otherwise you will potentially do useless copy when the member function takes parameters by value, and this will not work if parameters are rvalue. – Holt Jun 06 '18 at 19:18
  • @Holt Are you sure about that? It seems to work even if the parameter is rvalue-reference, and if we pass by value, will some kind of cast really optimize anything? Note that `Args` is deduced from the member function declaration and not from the parameters passed in. – super Jun 06 '18 at 19:25
  • See for yourself : https://godbolt.org/g/YV3ojF I know that `Args` is deduced from the structure parameters. Let's say you have a single arguments `T`, which can be (at least) `X`, `X &`, `X const&` or `X &&`. With `X&` and `X const&`, your code is okay. With `X`, you first do a copy when calling `c1(a);` and then another when you call `Foo::bar` from `operator()`. With `X&&`, you can call `operator()` using, e.g., `c1(std::move(a))`, but then you have an lvalue inside `operator()`, and you cannot pass it to `Foo::bar` since you cannot bind rvalue to lvalue. – Holt Jun 06 '18 at 19:29
  • @nitronoid No, but since it expects a pointer to a member function, and not an overload set you need to explicitly specify which overload you want. See [here](https://stackoverflow.com/questions/2942426/how-do-i-specify-a-pointer-to-an-overloaded-function) for an example. [Live demo](https://wandbox.org/permlink/4MtyWfXzcWfY6dp4). – super Jun 13 '18 at 10:08
1

I would like a function that combines different aspects of the above three functions [...]

If I understand correctly what do you want... It seems to me that is possible but I see only a convoluted solution.

Hoping someone else can propose a simpler way, I've used a couple of helper: a declared only template function gth1() to detect the Args... from the method pointer

template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;

and the specialization of a template gth2 struct with a static method that construct and return the lambda (with Holt's correction: thanks!)

template <typename, typename, auto>
struct gth2;

template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
 { 
   static auto getLambda (ClassT * obj)
    { return [obj](ArgsT ... args)
       { return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
 };

Now you can write a getCallback() function as follows

template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
 { return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); } 

The following is a full working example

#include <iostream>

template <typename, typename, auto>
struct gth2;

template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
 { 
   static auto getLambda (ClassT * obj)
    { return [obj](ArgsT ... args)
       { return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
 };

template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;

template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
 { return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); } 

// Example of use
struct Foo
 { void bar(size_t& x, const std::string& s) { x=s.size(); } };

int main ()
 {
   Foo f;

   auto l { getCallback<&Foo::bar>(&f) };

   size_t x;

   l(x, "1234567");

   std::cout << x << "\n";
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • You should remove the rvalue-reference modifier before `args...` in `operator()`, otherwise pass-by-value parameter will become rvalue-reference parameters, and you will chance the behavior of the final function. – Holt Jun 06 '18 at 19:20
  • @Holt - sorry... I admit that I have a lot of problem with left/right reference and forwarding, so you're probably right... but I don't understand where is the problem. Can you precise an example of wrong use case? – max66 Jun 06 '18 at 19:30
  • Since `Args` is a template parameter of the structure `gth2` and not of `operator()`, `args` is not a forwarding reference. You are basically applying an rvalue-modifier `&&` to an existing type (multiple in this case). If the type is a (const-)reference, then by the standard rules, everything is okay because `X & &&` is `X &`. If you have an rvalue, that's also okay because `X && &&` is `X &&`. But if you have a non-reference type `X`, then `X&&` is an rvalue-reference to `X`, so you have different signatures for `operator()` and the original `Foo::bar`. See https://godbolt.org/g/NnkMm9 – Holt Jun 06 '18 at 19:33
  • @Holt - One of this day perfect forwarding will drive me crazy. Yes: you're right: changing the type of the first argument of `Foo::bar()` from `std::size_t &` to `std::size_t` I get an error. Corrected (I hope). Thanks. – max66 Jun 06 '18 at 19:45
  • You don't want the rvalue-modifiers, but you still need to `std::forward` (otherwise it's rvalue-reference parameters that won' work) ;) See the discussion in the comments of super's answer. – Holt Jun 06 '18 at 19:47
  • @ArthurTacca - IMHO, the super's one is better because less convoluted. – max66 Jun 07 '18 at 09:48
0

Here's another possibility. It's inspired by the other answers, but while those both use some partial template specialization, this one only uses function template argument deduction.

The inner function takes a second parameter whose type is used for this deduction, and its runtime value is equal to the compile time value of the non-type template parameter. It is ignored at runtime, and in particular it isn't captured by the lambda.

template <auto memFn, class ClassT, class RetT, class... ArgsT>
inline auto getCallbackInner(ClassT* obj, RetT(ClassT::*)(ArgsT...))
{
    return [obj](ArgsT... args)->RetT {
        return (obj->*memFn)(std::forward<ArgsT>(args)...);
    };
}
template <auto memFn, class ClassT>
auto getCallback(ClassT* obj)
{
    return getCallbackInner<memFn, ClassT>(obj, memFn);
}

As with the other two answers, this one still uses an auto template parameter from the C++17 standard, so it doesn't work in Visual Studio. This is a pity but it seems it's just not possible with just C++14.

Footnote:

Yet another answer, which is more subjective but probably most correct, is that I should just not try to pass the member function as a template parameter in the first place. The original getCallbackPtr(), which simply binds a member function pointer into the lambda, is likely to be understood by more humans (as well as more compilers) than any other possibility. The performance cost of indirection through a member function pointer is likely to be insignificant while the maintenance cost of using template tricks is high, so I think I will actually use that version in practice, unless there is demonstrable performance cost.

Arthur Tacca
  • 8,833
  • 2
  • 31
  • 49