I come from the python world where I could define a chain of operations and call them in a for loop:
class AddOne:
def __call__(self, x, **common_kwargs):
return x+1
class Stringify:
def __call__(self, x, **common_kwargs):
return str(x)
class WrapNicely:
def __call__(self, s, **common_kwargs):
return "result="+s
data = 42
for operation in [AddOne(), Stringify(), WrapNicely()]:
data = operation(data)
output = data
(Note: the goal is to have complex operations. Ideally, common kwargs could be given)
What would be the equivalent in C++ if the return type can be different after each call?
I'm not sure I could find anything close but I may have search with wrong keywords…
C++ is statically typed, so options here are limited:
std::variantFor the first alternative you could create a class template that executes functions via recursive calls, but it's a bit more complex than your python code:
template<class...Fs>
class Functions
{
std::tuple<Fs...> m_functions;
template<size_t index, class Arg>
decltype(auto) CallHelper(Arg&& arg)
{
if constexpr (index == 0)
{
return std::forward<Arg>(arg);
}
else
{
return std::get<index - 1>(m_functions)(CallHelper<index - 1>(std::forward<Arg>(arg)));
}
}
public:
Functions(Fs...functions)
: m_functions(functions...)
{
}
template<class Arg>
decltype(auto) operator()(Arg&& arg)
{
return CallHelper<sizeof...(Fs)>(std::forward<Arg>(arg));
}
};
int main() {
Functions f{
[](int x) { return x + 1; },
[](int x) { return std::to_string(x); },
[](std::string const& s) { return "result=" + s; }
};
std::cout << f(42) << '\n';
}
Note: This requires the use of a C++ standard of at least C++17.
Use composition from ranges:
using std::views::transform;
auto fgh = transform(h) | transform(g) | transform(f);
auto fgh_x = std::array{42} | fgh; // Calculate f(g(h(x)))
// single element range ^^
// ^^ ranges::single_view{42} is an alternative
std::cout << fgh_x[0]; // Result is the only element in the array.
Demo
I've written a series of articles on C++ functional programming years ago, for some thoughts on composition you can start from this one.
That said, you can also avoid the "functional nuances" and start from scratch. Here is a generic composer of callables:
template <class F, class... Fs>
auto composer(F&& arg, Fs&&... args)
{
return [fun = std::forward<F>(arg),
...functions = std::forward<Fs>(args)]<class X>(X&& x) mutable {
if constexpr (sizeof...(Fs))
{
return composer(std::forward<Fs>(functions)...)(
std::invoke(std::forward<F>(fun), std::forward<X>(x)));
}
else
{
return std::invoke(std::forward<F>(fun), std::forward<X>(x));
}
};
}
which you'd use as:
// Store the composed function or call it right away.
composer(lambda1, lambda2, lambda3)(42);
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With