2
votes

I'm trying to create template functor, which will take as arguments object and member function with any number of parameters. I can't figure out how to write the code correctly with templates.

template<typename ItemT,
     class T,
     typename ...Args>
struct Builder
{
    ItemT operator()(T& object, ItemT (T::*method)(Args...), Args && ... args)
    {
        return (object.*method)(std::forward<Args>(args)...);
    }
};

struct Object
{
    int method(int, int, int) { return 4; }
};


int main()
{
    Object obj;    
    Builder<int, Object>()(obj, &Object::method); // Error here
}

If I make Object::method with no parameters - code compiles. But with parameters - no.

Severity Code Description Project File Line Suppression State Error C2664 'int Builder::operator ()(T &,ItemT (__thiscall Object::* )(void))': cannot convert argument 2 from 'int (__thiscall Object::* )(int,int,int)' to 'int (__thiscall Object::* )(void)' drafts c:\drafts\main.cpp 139

2
You forgot to pass to define what typename ...Args is when you declared the Builder object. - NathanOliver
You need Builder<int, Object, int, int, int>, and you need to actually pass the three ints that Object::method wants as arguments. More generally, it's not clear what the point of the exercise is. What problem are you really trying to solve? - Igor Tandetnik
What Nathan said. This is also the reason the standard library provides various "make_*" free functions that just construct objects. So deduction of the template arguments can happen. With c++1z, constructors will also be able to deduce template parameters for the class, making your code valid. - StoryTeller - Unslander Monica

2 Answers

4
votes

Assuming you don't want to change the current definition of Builder, this is how you need to instantiate it:

Builder<int, Object, int, int, int>()(obj, &Object::method, 0, 0, 0);
//      ^       ^    ^^^^^^^^^^^^^                          ^^^^^^^
//      ItemT   |    |                                      |
//              T    Args...                                args...

The args... parameter expansion in the operator() call must match the TArgs... pack passed to Builder itself.

wandbox example


Here's an alternative less strict design:

template<typename T>
struct Builder
{
    template <typename TFnPtr, typename... Args>
    auto operator()(T& object, TFnPtr method, Args && ... args)
    {
        return (object.*method)(std::forward<Args>(args)...);
    }
};

The above Builder can be used like this:

int main()
{
    Object obj;    
    Builder<Object>()(obj, &Object::method, 0, 0, 0);
}

In this case the type of member function pointer is deduced through TFnPtr and not constrained to any particular set of parameters. The variadic parameters are not part of Builder anymore - they're part of Builder::operator(), so they can be deduced and forwarded to (object.*method).

wandbox example

1
votes

You can avoid templating on Builder altogether and solely rely on template argument deduction:

struct Builder {
    template <typename Obj, typename R, typename ... FArgs, typename ... Args>
    R operator()(Obj& obj, R (Obj::*fn)(FArgs...), Args&&... args) {
        return (obj.*fn)(std::forward<Args>(args)...);
    }
};

I chose to use two parameter packs to allow perfect forwarding: the value categories of the operator() call do not necessarily match the targeted method. This also allows for implicit conversion of arguments when applying the member function pointer. Note that this implementation will not match const methods of Obj.

You can of course relax it a bit using auto return type (C++14) or trailing return type (C++11). Since Vittorio's answer already presented you the C++14 way, I'll tackle the latter. The operator() then becomes:

template <typename Obj, typename FnPtr, typename ... Args>
auto operator()(Obj& obj, FnPtr fn, Args&&... args)
    -> decltype((obj.*fn)(std::forward<Args>(args)...)) {
    return (obj.*fn)(std::forward<Args>(args)...);
}

Then, usage will simply be:

Object obj;
Builder()(obj, &Object::method, 0, 0, 0);

live demo on Coliru.