Yurii Komarnytskyi
Yurii Komarnytskyi

Reputation: 173

Template functor with any parameters

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

Upvotes: 2

Views: 1289

Answers (2)

Rerito
Rerito

Reputation: 6086

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.

Upvotes: 1

Vittorio Romeo
Vittorio Romeo

Reputation: 93324

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

Upvotes: 4

Related Questions