Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to `std::bind()` a standard library algorithm?

The short version of my question is this: How can I use something like std::bind() with a standard library algorithm?

Since the short version is a bit devoid of details, here is a bit of an explanation: Assume I have the algorithms std::transform() and now I want to implement std::copy() (yes, I realize that there is std::copy() in the standard C++ library). Since I'm hideously lazy, I clearly want to use the existing implementation of std::transform(). I could, of course, do this:

struct identity {
    template <typename T>
    auto operator()(T&& value) const -> T&& { return std::forward<T>(value); }
};  
template <typename InIt, typename OutIt>
auto copy(InIt begin, InIt end, OutIt to) -> OutIt {
    return std::transform(begin, end, to, identity());
}

Somehow this implementation somewhat feels like a configuration of an algorithm. For example, it seems as if std::bind() should be able to do the job but simply using std::bind() doesn't work:

namespace P = std::placeholders;
auto copy = std::bind(std::transform, P::_1, P::_2, P::_3, identity());

The problem is that the compiler can't determine the appropriate template arguments from just the algorithm and it doesn't matter if there is an & or not. Is there something which can make an approach like using std::bind() work? Since this is looking forward, I'm happy with a solution working with anything which is already proposed for inclusion into the C++ standard. Also, to get away with my laziness I'm happy to do some work up front for later easier use. Think of it this way: in my role as a library implementer, I'll put things together once such that every library user can be lazy: I'm a busy implementer but a lazy user.

In case you want to have a ready-made test bed: here is a complete program.

#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>

using namespace std::placeholders;

struct identity {
    template <typename T>
    T&& operator()(T&& value) const { return std::forward<T>(value); }
};


int main()
{
    std::vector<int> source{ 0, 1, 2, 3, 4, 5, 6 };
    std::vector<int> target;

#ifdef WORKS
    std::transform(source.begin(), source.end(), std::back_inserter(target),
                   identity());
#else
    // the next line doesn't work and needs to be replaced by some magic
    auto copy = std::bind(&std::transform, _1, _2, _3, identity());
    copy(source.begin(), source.end(), std::back_inserter(target));
#endif
    std::copy(target.begin(), target.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << "\n";
}
like image 275
Dietmar Kühl Avatar asked Dec 29 '14 18:12

Dietmar Kühl


1 Answers

When trying to std::bind() an overloaded function the compiler can't determine which overload to use: at the time the bind()-expression is evaluated the function arguments are unknown, i.e., overload resolution can't decide which overload to pick. There is no direct way in in C++ [yet?] to treat an overload set as an object. Function templates simply generate an overload set with one overload for each possible instantiation. That is, the entire problem of not being able to std::bind() any of the standard C++ library algorithms revolves around the fact that the standard library algorithms are function templates.

One approach to have the same effect as std::bind()ing an algorithm is to use C++14 generic lambdas to do the binding, e.g.:

auto copy = [](auto&&... args){
    return std::transform(std::forward<decltype(args)>(args)..., identity());
};

Although this works it is actually equivalent to a fancy implementation of function template rather than configuring an existing function. However, using generic lambdas to create the primary function objects in a suitable standard library namespace could make the actual underlying function objects readily available, e.g.:

namespace nstd {
    auto const transform = [](auto&&... args){
        return std::transform(std::forward<decltype(args)>(args...));
    };
}

Now, with the approach to implementing transform() it is actually trivial to use std::bind() to build copy():

auto copy = std::bind(nstd::transform, P::_1, P::_2, P::_3, identity());

Despite the looks and use of generic lambdas it is worth pointing out that it actually takes roughly the same effort to create corresponding function objects using only features available for C++11:

struct transform_t {
    template <typename... Args>
    auto operator()(Args&&... args) const
        -> decltype(std::transform(std::forward<decltype(args)>(args)...)) {
        return std::transform(std::forward<decltype(args)>(args)...);
    }
};
constexpr transform_t transform{};

Yes, it is more typing but it is just a reasonable small constant factor over the use of generic lambdas, i.e., if the objects using generic lambdas the C++11 version is, too.

Of course, once we have function objects for the algorithms it may be neat to actually not even having to std::bind() them as we'd need to mention all the not bound arguments. In the example case it is currying (well, I think currying only applies to binding the first argument but whether it's the first or the last argument seems a bit random). What if we had curry_first() and curry_last() to curry the first or the last argument? The implementation of curry_last() is trivial, too (for brevity I'm using a generic lambda but the same rewrite as above could be used to make it available with C++11):

template <typename Fun, typename Bound>
auto curry_last(Fun&& fun, Bound&& bound) {
    return [fun = std::forward<Fun>(fun),
            bound = std::forward<Bound>(bound)](auto&&... args){
        return fun(std::forward<decltype(args)>(args)..., bound);
    };
}

Now, assuming that curry_last() lives in the same namespace a either nstd::transform or identity() the definition of copy() could become:

auto const copy = curry_last(nstd::transform, identity());

OK, maybe this question didn't get me any hat but maybe I'll get some support for turning our standard library algorithms into function objects and possibly adding a few cool approaches to creating bound versions of said algorithms. I think this approach is much saner (although in the form described above possibly not as complete) than some of the proposals in this area.

like image 84
Dietmar Kühl Avatar answered Sep 24 '22 20:09

Dietmar Kühl