Generalized Function Evaluation

I often find myself wishing there was a generalized way of calling functions and function-like “things”.

Functors are obviously quite function-like. Member function pointers can be treated like function where the object pointer as just another parameter. Member object pointers can be treated like a function which retrieves a member from a pointed object.

Now with the new c++11 standard, it is possible to do this quite easily!

#include <type_traits>
#include <utility>

// functions, functors, lambdas, etc.
template<
    class F, class... Args,
    class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type,
    class = typename std::enable_if<!std::is_member_object_pointer<F>::value>::type
    >
auto eval(F&& f, Args&&... args) -> decltype(f(std::forward<Args>(args)...))
{
    return f(std::forward<Args>(args)...);
}

// const member function
template<class R, class C, class... Args>
auto eval(R(C::*f)() const, const C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

template<class R, class C, class... Args>
auto eval(R(C::*f)() const, C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

// non-const member function
template<class R, class C, class... Args>
auto eval(R(C::*f)(), C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

// member object
template<class R, class C>
auto eval(R(C::*m), const C& c) -> const R&
{
    return c.*m;
}

template<class R, class C>
auto eval(R(C::*m), C& c) -> R&
{
    return c.*m;
}

The first overload of eval covers almost every single case. Furthermore, note the use of universal references and forwarding which automatically handles const and reference qualifiers.

The next three overloads handle const and non-const member function pointers. Additional parameters of C& are added as if they were parameters of the original functions.

Member object pointers are then treated as functions which take a reference to an object then return a reference to its member.

The use of std::enable_if prevents the first overload from greedily instantiating for member function pointers.

Now to show it in action:

#include <iostream>

struct Bloop
{
    int a = 10;
    int operator()(){return a;}
    int operator()(int n){return a+n;}
    int triple(){return a*3;}
};

int add_one(int n)
{
    return n+1;
}

int main()
{
    Bloop bloop;

    // free function
    std::cout << eval(add_one,0) << "\n";

    // lambda function
    std::cout << eval([](int n){return n+1;},1) << "\n";

    // functor
    std::cout << eval(bloop) << "\n";
    std::cout << eval(bloop,4) << "\n";

    // member function
    std::cout << eval(&Bloop::triple,bloop) << "\n";

    // member object
    eval(&Bloop::a,&bloop)++; // increment a by reference
    std::cout << eval(&Bloop::a,bloop) << "\n";

    return 0;
}

Which gives the expected output:

1
2
10
14
30
11

Note: Be careful with pointers to overloaded functions, the compiler will not be able to determine which overload to use. You will need to either explicitly cast to a function pointer or std::function object with the desired type.

Edit: Thank you to STL for pointing out member object pointers should return references, not values.

Edit2: Thanks to Aaron McDaid for pointing out a few more mistakes.

9 comments

    • whanhee

      Apply, call, evaluate… I just picked one. Technically, this is an implementation of INVOKE from the standard (20.10.2).

      Also, don’t call me Shirley.

      • Grout

        > I often find myself wishing there was a generalized way of calling functions and function-like “things”.

        Well, there is. It’s called postfix (). Or can eval() do something postfix () can’t?

      • whanhee

        operator() will only work for free functions and functors. Also, the syntax for member function pointers is strange…

        Did you read post?

  1. Aaron McDaid (@aaronmcdaid)

    A couple of typos. The last two examples should pass in ‘bloop’, not ‘&bloop’, as your member object overloads are expecting references, not pointers.

    The enable_if does not appear to be necessary. I commented it out and it worked fine for me. I believe that this works because, if the compiler fails to deduce the return type (inside your “->dlcltype(f(std::forward(args)…))” here, then the overload will be discarded.

    Also, I don’t understand your use of pointers here. You have two const member functions, allowing the `this` object to be passed in by reference or by pointer. To be consistent, you might want to allow this flexibility with the data member functions also? Is this deliberate? Perhaps this is left as an exercise to the reader 🙂

    • whanhee

      Ahh, thanks for pointing those out. A few of these are relics of my old code where I was playing with possibly allowing smart pointers as well as raw pointers. I ended up going with just references, but forgot to update that here, that should be fixed now 🙂

      As to your second point, in another version I wrote, I removed the trailing return types and got a few cases where the general case instantiated over specific cases. You are correct in that it’s redundant, but I keep it as a reminder in case I try that again.

  2. Sheljohn

    Sorry for my ignorance, but isn’t the second implementation of `eval` for const member function useless? A const member function will not modify the instance `c` (apart from mutable stuff, which can mutate even if `c` is const anyway), so const or non-const input should not matter.. In this case const is less restrictive, and in my opinion sufficient, isn’t it?

    • whanhee

      Const types are considered different from their non-const variants and this sometimes confuses the template/type system. Without the second implementation I found that it occasionally didn’t work, but honestly, that’s a dark corner I don’t know in full detail.

  3. Bengi Mizrahi

    How can you declare template function pointer f like R(C::*f)() with no arguments and still be able to call it with Args…?

Leave a comment