Better Function Objects in C++11

At the 2013 Meeting C++ conference, Eric Niebler argued in his keynote that we should be using function objects more often in C++. If I recall correctly, his argument was that function objects don’t participate in ADL.

Function objects have another advantage. They are easier to use in std::bind. If you pass a function pointer or a pointer to a member function to std::bind et al, you have to specify the exact function overload. Given

template<class T> 
struct foo {
    int bar(int a, int b) const { ... }
    int bar(int a, int b) { ... }
};

the following statement creates a unary function that takes an int x as argument and calls the const overloaded member function f.bar(0, x) on that instance:

foo<int> f;
auto fn_bar = std::bind(
    static_cast< int (foo<int>::*)(int, int) const >(
        &foo<int>::bar
    ), 
    f, 0, std::placeholders::_1
);

Eric advocated writing function objects like this instead:

struct bar_ {
    template<typename Foo>
    auto operator()(Foo&& foo, int a, int b) const 
        -> decltype(foo.bar(a, b)) 
    {
        return foo.bar(a, b);
    }
} bar;

The above bind can now be written as:

auto fn_bar = std::bind(bar, f, 0, std::placeholders::_1);

Much better. But wouldn’t it be better still if we could create a unary function object by writing

auto fn_bar = bar(f, 0, std::placeholders::_1); // 1

or even — let’s go crazy — by writing this:

auto fn_bar = bar(f, 0); // 2

Since bar is a ternary function that is only called with two arguments, it turns into a unary function fn_bar. Calling fn_bar(1) expands to bar(f, 0, 1). That is called currying and is supported e.g. in Scala and other functional programming languages.

All we need for this to work is a wrapper around our struct bar_ that can detect at compile time if

  1. bar is called with arguments that contain a std::placeholder
  2. bar is called with too few arguments

In these cases the object returned from a call to bar is actually a std::bind object, i.e. a function object. The first style of self-binding function objects has been implemented by Eric as part of his (experimental) range library. I’ve implemented the second style of function objects that automatically curry when the number of arguments is too low.

Eric’s imple mentation supports chained function objects, it seems, i.e. calling f(g(std::placeholders::_1), 1, 2) should become a unary function. I didn’t get to that.

While I like the idea of leaving out the placeholders too, my own approach suffers from a serious drawback at the moment. My solution is, like Eric’s, based on std::bind and that needs to be passed the desired number of placeholders. Therefore my function object wrapper needs to know how many placeholders to pass to std::bind when it is called with n arguments. That means my function wrapper needs to know the arity of the wrapped function. There is no way to determine the arity of struct bar_ at compile time. bar_ could overload operator() of course with different numbers of arguments, so there isn’t a defined single function arity to begin with.

The solution: Remove my dependency on std::bind.

To be continued

7 Gedanken zu “Better Function Objects in C++11

  1. I think there’s some typos in the definition of struct bar_{…} bar;. Shouldn’t it be like this?

    struct bar_ {
    template
    auto operator()(Foo&& foo, int a, int b) const
    -> decltype(foo.bar(a, b))
    {
    return foo.bar(a, b);
    }
    } bar;

  2. With regard to automatically currying, I suggested just such a thing on the Boost mailing list in 2011. I even posted code. It was considered a bad idea. Thing is, in functional languages, there is no difference between a nullary function and the result of calling that function, but in C++ there is. This was a major a-ha! moment for me. You can read the whole thread [here](http://thread.gmane.org/gmane.comp.lib.boost.devel/222736/focus=222736).

    • I’ll check out the thread and I’ll enable Markdown comments.

      I don’t quite understand your argument (yet). I see how a nullary function in C++ must be explicitly called at some point, while functional programming languages can practically decide automatically when a function is actually evaluated.

      Maybe I’ll read the thread first :-)

      That said, the fact that using placeholders permits the permutation of arguments and provides some documentation that you’re creating a function object is in itself a value.

  3. What is the point of all this complexity when you can just use a lambda function wrapper to do all this in 1 line of code?
    auto bar = [](int x) { return f.bar(0, x); };

    I feel academically your post is interesting, but practically it is over-engineering code, and things should just be written simple where possible.

    • First of all, I won’t deny that I did this partly because it is academically interesting!

      Secondly, the lambda isn’t always a better or even feasible choice. Until C++14, lambdas don’t support auto arguments, function objects do support a templated operator(). Lambdas can’t be overloaded, function objects can be.

      Thirdly, when the argument types are very long, it might just be shorter to write std::bind(mem_fn_Something(), _1) instead of the actual argument types.

      So in general, you’re right of course. Things should be programmed in the simplest possible way. But sometimes this means implementing a generic (and assumed correct), complex functionality in a library which enables programmers to write something very simple. If my little experiment here makes the right trade-offs between complexity and simplicity is up for debate, see Eric’s comments.

Kommentar verfassen