I'd avoid direct Java code translation.
One C++ way is to embrace values.
template<class Sig>
struct adapter;
template<class Out, class In>
struct adapter<Out(In)>:std::function<Out(In)>
{
using std::function<Out(In)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In in)->Result {
return next(self(std::forward<In>(in)));
};
}
};
there we go.
adapter<int(double)> rounder=[](double d){return std::floor(d);};
adapter<double(std::istream&)> double_reader=[](auto&is){double d; is>>d; return d;};
adapter<int(std::istream&)> int_reader=double_reader.chain(rounder);
std::cout << int_reader(std::cin);
etc.
These adapters are polymorphic value types. And you can chain them with function pointers, lambdas, std functions, or other function objects
The x.adapt(foo)
call is spelled x(foo)
in the above code. Objects whose primary purpose is to be called ... can use operator()
to make then callable.
Live example.
Design:
I made std::function
do most of the heavy lifting. It is a polymorphic value type that supports calling and nullability.
We simply inherit from it, forward constructors, and add in a .chain
method that checks compatibility and deduces the return type.
We can extend adapters to support many-one adaption easily. The first step is to support multiple inputs just by adding a bunch of ...
s in the right spot:
template<class Sig>
struct adapter;
template<class Out, class... In>
struct adapter<Out(In...)>:std::function<Out(In...)>
{
using std::function<Out(In...)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In...)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In...)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In... in)->Result {
return next(self(std::forward<In>(in)...));
};
}
};
Now, the first.chain(second)
syntax doesn't work as well with multiple inputs for second
. We can add another method:
template<class Sig>
struct adapter;
template<class Out, class... In>
struct adapter<Out(In...)>:std::function<Out(In...)>
{
using std::function<Out(In...)>::function;
template<class Next, class Result=std::invoke_result_t<Next const&, Out>>
adapter<Result(In...)> chain( Next next ) const& {
auto tmp=*this;
return std::move(tmp).chain(std::move(next));
}
template<class Next, class Result=std::invoke_result_t<Next, Out>>
adapter<Result(In...)> chain( Next next ) && {
return [self=std::move(*this), next=std::move(next)](In... in)->Result {
return next(self(std::forward<In>(in)...));
};
}
template<class...First>
adapter<Out(First...)> consume( adapter<In(First)>... src)&&
{
return [self=std::move(*this), ...src=std::move(src)](First... first)->Out
{
return self(src(std::forward<First>(first))...);
};
}
template<class...First>
adapter<Out(First...)> consume( adapter<In(First)>... src) const&
{
auto tmp = *this;
return std::move(tmp).consume( std::move(src)... );
}
};
but that is going a bit far, no?
Live example.
There are some who are leery about inheriting from a value type (like std::function
) in a non-polymorphic manner. Here, I'd argue that almost nobody in their right mind stores pointers to std::function
s; those that do, often are doing a shared_ptr
trick (which handles polymorphic destruction).
However, if you are concerned, you can do this:
template<class Out, class... In>
struct adapter<Out(In...)>
{
using F = std::function<Out(In...)>;
F f;
// forward call operator
template<class...Args>
auto operator()(Args&&...args)const
-> std::invoke_result_t<F const&, Args&&...>
{
return f(std::forward<Args>(args)...);
}
// default all special member functions:
adapter()=default;
adapter(adapter const&)=default;
adapter(adapter &&)=default;
adapter& operator=(adapter const&)=default;
adapter& operator=(adapter &&)=default;
~adapter()=default;
// forward a few useful operations and ctors:
explicit operator bool() const { return (bool)f; }
template<class Fin>
requires (!std::is_same_v<Fin, adapter> && std::convertible_to<Fin, F>)
adapter( Fin fin ):f(std::forward<Fin>(fin)) {}
then add in the .chain
methods.
As you can see, this adds a fair bit of code. Live example.