I'm a mathematician used to doing "old style" C++ programming for a long time now. I feel that some new syntactic constructions offerred by C++11 could help me achieve some better code regarding my professionnal projects. Yet as I'm no CS professionnal I must confess that I lack the knowledge to understand some examples I encounter in my self-learning process, altough I've been pretty lucky/succesful so far.
My impression is that variadic templates can be used to implement type-safe functions composition, as in this question. My concern is slightly more general since I'd like to compose functions with heterogeneous (but compatible) argument/return types. I've googled a lot and found another reference, but it seems utter "black magic" to me ;) and I won't pretend I can adapt the code in my context, although I feel I should find what I need in there.
I think the (most incomplete) code below is relatively self-explanatory as to what I'd like to achieve. In particular I believe the proper implementation will throw a compile-time error when one's trying to compose incompatible functions (Arrow here), and will need a piece of recursive template code.
template <typename Source , typename Target> class Arrow
{
Target eval (const Source &);
};
template <typename ...Arrows> class Compositor
{
template <typename ...Arrows>
Compositor (Arrows... arrows)
{
// do/call what needs be here
};
auto arrow(); // gives a function performing the functionnal composition of arrows
};
// define some classes A, B and C
int main(int argc, char **argv)
{
Arrow < A , B > arrow1;
Arrow < B , C > arrow2;
Compositor< Arrow < A , B > , Arrow < B , C > > compositor(arrow1 , arrow2);
Arrow < A , C > expected_result = compositor.arrow();
}
Ideally I'd like
Compositor
to directly subclass
Arrow < source_of_first_arrow , target_of_last_arrow>
and the method
arrow()
be replaced by the corresponding
eval()
but I felt the above code was more explanatory.
Any help will be greatly appreciated, even if it consists in a rough rebuke with a pointer to an existing (relatively basic) piece of example which will surely have escaped my search. Thanks!
If I got it correctly, you need no fancy template magic to do this composition. Here is the almost self-explanatory code:
#include <functional>
#include <string>
#include <iostream>
// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;
// composition operator: just create a new composed arrow
template <typename A, typename B, typename C>
Arrow<A, C> operator*(const Arrow<A, B>& arr1, const Arrow<B, C>& arr2)
{
// arr1 and arr2 are copied into the lambda, so we won't lose track of them
return [arr1, arr2](const A& a) { return arr2(arr1(a)); };
}
int main()
{
Arrow<int, float> plusHalf([](int i){return i + 0.5;});
Arrow<float, std::string> toString([](float f){return std::to_string(f);});
auto composed = plusHalf * toString; // composed is Arrow<int, std::string>
std::cout << composed(6) << std::endl; // 6.5
//auto badComposed = toString * plusHalf; // compile time error
}
I mostly played with lambda functions here.
Using a single function call instead of a operator chain proved to be a more tricky problem. This time you got some templates:
#include <functional>
#include <string>
#include <iostream>
// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;
// A helper struct as template function can't get partial specialization
template <typename... Funcs>
struct ComposerHelper;
// Base case: a single arrow
template <typename A, typename B>
struct ComposerHelper<Arrow<A, B>>
{
static Arrow<A, B> compose(const Arrow<A, B>& arr)
{
return arr;
}
};
// Tail case: more arrows
template <typename A, typename B, typename... Tail>
struct ComposerHelper<Arrow<A, B>, Tail...>
{
// hard to know the exact return type here. Let the compiler figure out
static auto compose(const Arrow<A, B>& arr, const Tail&... tail)
-> decltype(arr * ComposerHelper<Tail...>::compose(tail...))
{
return arr * ComposerHelper<Tail...>::compose(tail...);
}
};
// A simple function to call our helper struct
// again, hard to write the return type
template <typename... Funcs>
auto compose(const Funcs&... funcs)
-> decltype(ComposerHelper<Funcs...>::compose(funcs...))
{
return ComposerHelper<Funcs...>::compose(funcs...);
}
using namespace std;
int main()
{
Arrow<int, float> plusHalf([](int i){return i + 0.5;});
Arrow<float, string> toString([](float f){return to_string(f);});
Arrow<string, int> firstDigit([](const string& s){return s[0]-'0';});
auto composed = compose(plusHalf, toString, firstDigit);
// composed is Arrow<int, int>
std::cout << composed(61) << std::endl; // "6"
//auto badComposed = compose(toString, plusHalf); // compile time error
}
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