3

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?

Enlico
  • 23,259
  • 6
  • 48
  • 102

3 Answers3

4

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);
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Hi, sorry for the late feedback, but I forgot about this question. Your answer is excellent, considering I did not write _without using macros_. Would you say what I ask is simply **_not_** possible without macros? – Enlico Feb 10 '21 at 19:18
  • @Enlico No worries. I don't think this can be done without a macro. You can't pass an overloaded function to another function (this is why you are asking the question) so that route is out, which, AFAIK, only leaves macros as your option for something that is automatic. Macros aren't evil, they have legitimate uses, like this use case. – NathanOliver Feb 10 '21 at 19:21
  • Thanks. As I wrote the question, at that time, I realized that I was basically telling myself why it's not possible, but having somone knowledgeable confirm it's always good! Accepted. – Enlico Feb 10 '21 at 19:25
  • @Enlico You're welcome. C++ is pretty complicated, so it never hurts to get a second, third or even fourth opinion. :) – NathanOliver Feb 10 '21 at 19:26
  • What is the importance of `noexcept`² here? – Enlico Jun 23 '21 at 10:37
  • @Enlico `noexcept(func(std::forward(val)...))` says tell me the if the function called in this way is no except or not. `noexcept(all_that_stuff)` says give my function the same exception specification as that function call. This is important since if the lambda was `noexcept` but `func` isn't, then if an exception is thrown the program hard exits. – NathanOliver Jun 23 '21 at 12:01
  • Oh, so the inner `noexcept` is the [`noexcept` operator](https://en.cppreference.com/w/cpp/language/noexcept), whereas the outer is the [`noexcept` specifier](https://en.cppreference.com/w/cpp/language/noexcept_spec). Thanks for the explanation! – Enlico Jun 25 '21 at 09:59
1

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 ).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

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);`
Enlico
  • 23,259
  • 6
  • 48
  • 102