Most terse and reusable way of wrapping template or overloaded functions in function objects

Viewed 217

Scenario 1: a template function pred

template<typename T>
bool pred(T t) { /* return a bool based on t */ }

Scenario 2: a set of functions overloaded on the same name pred

bool pred(A t) { /* return a bool based on t */ }
bool pred(B t) { /* return a bool based on t */ }
bool pred(C t) { /* return a bool based on t */ }
...

Whichever of the two scenarii we're in, the bottom line is that pred does not refer to a function, and so it cannot be passed around, e.g. as a unary predicate to std::remove_if.

Therefore it is convenient in this case to define the following object which can be passed around instead,

auto constexpr predObj = [](auto t){ return pred(t); };

However, as soon as I have a similar need for another unary predicate, I need to copy and paste that line and change the two names to something else; similarly if I need to do that for a binary predicate:

auto contexpr binPredObj = [](auto x, auto y){ return binPred(x, y); };

Is there a general way of making this automatically? I'm thinking of something like

auto funObj = fun2Obj(fun);

I have the feeling that what I ask is not possible exactly because it would require passing fun as it was a function object, which it isn't, otherwise I wouldn't need to make a function object out of it. But asking is never crime, right?

3 Answers

You can create a macro like

#define FUNCTORIZE(func) [](auto&&... val) \
noexcept(noexcept(func(std::forward<decltype(val)>(val)...))) -> decltype(auto) \
{return func(std::forward<decltype(val)>(val)...);}

which will let you wrap any callable into a closure object. You would use it like

auto constexpr predObj = FUNCTORIZE(pred);

Here is a macro that does proper SFINAE, noexcept, and forwarding:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define CALLER_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__(std::forward<decltype(args)>(args)...)) )

#define CALLER_OF_WITH_CAPTURE(...) \
  [&](auto&&...args) \
  RETURNS( __VA_ARGS__(std::forward<decltype(args)>(args)...)) )

auto constexpr predObj = CALLER_OF(pred);

there has been a proposal to make this built into the language as follows:

auto constexpr predObj = (args...) => pred(std::forward<decltype(args)>(args)...);

but that was rejected.

CALLER_OF_WITH_CAPTURE (and @Barry's proposal) has a quirk in that the noexcept/decltype return test is done not in the context of any captures, while the body is done in the context of any captures. So CALLER_OF_WITH_CAPTURE can have surprising errors from this.

In CALLER_OF, the argument can be any series of tokens, except an unbalanced closing ).

After some time, I had a chat with a colleague about this topic, and he made me aware that there's a Boost library offering this very functionality by means of two macros named BOOST_HOF_LIFT and BOOST_HOF_LIFT_CLASS.

Example:

#include <boost/hof.hpp>
#include <cassert>
#include <algorithm>

// Declare the class `max_f`
BOOST_HOF_LIFT_CLASS(max_f, std::max);

int main() {
    auto my_max = BOOST_HOF_LIFT(std::max);
    assert(my_max(3, 4) == std::max(3, 4));
    assert(max_f()(3, 4) == std::max(3, 4));
}

Also, here's a nice blog post about the topic, which also points to the same proposal that Yakk - Adam Nevraumont's answer points to.


My question is about templated and/or overloaded functions, because that's a "category" of "named things" that you can't pass around.

However, there's also another category: constructors. You cant' pass a constructor as the operator to the std::transform algorithm, for instance.

Well, Boost.Hof helps in this respect too, via the boost::hof::construct template, which is such that assert(construct<T>()(xs...) == T(xs...)); holds.

It can be used like this

#include <boost/hof/construct.hpp>

struct A {
  A(int) {}      // some...
  A(int, int) {} // ... constructors
};

auto makeA = boost::hof::construct<A>();

// now `makeA` is the "objectified" version of `A`, so you could use it like this:
std::transform(vecOfInts.begin(), vecOfInts.end(), vecOfAs.begin(), makeA);`
Related