I stumbled upon this nice blog post – pipable functions in C++14. This is realy fun idea as its usage plain for anybody who is familiar with unix pipelines. So i tried to use it in C++11 (without boost) from slightly different angle to make it more real-life concept.
First sample (fun, but not so interesting):
1 2 3 |
auto add = piped([](int x, int y){ return x + y; }); auto mul = piped([](int x, int y){ return x * y; }); int y = 5 | add(2) | mul(5) | add(1); // Output: 36 |
Second sample (functional style array processing):
1 2 3 |
vector<int> numbers{4,8,15,16,23,42}; auto result = numbers | where([](int x){ return (x > 10); }) | map([](int x){ return x + 5; }) | log(); // List: 20 21 28 47 |
Simple and short implementation is under the cut…
To implement the first sample you need only the following short number of lines (C++11 without boost):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
template <class F> struct pipeable { private: F f; public: pipeable(F&& f) : f(std::forward<F>(f)) {} template<class... Xs> auto operator()(Xs&&... xs) -> decltype(std::bind(f, std::placeholders::_1, std::forward<Xs>(xs)...)) const { return std::bind(f, std::placeholders::_1, std::forward<Xs>(xs)...); } }; template <class F> pipeable<F> piped(F&& f) { return pipeable<F>{std::forward<F>(f)}; } template<class T, class F> auto operator|(T&& x, const F& f) -> decltype(f(std::forward<T>(x))) { return f(std::forward<T>(x)); } |
You can see a lot of C++11 features here coupled together – perfect forwarding, &&, std::bind, auto and decltype. Using this you can even define functions inside pipeline:
1 |
int y2 = 5 | add(2) | piped([](int x, int y){ return x * y; })(5) | piped([](int x){ return x + 1; })(); // Output: 36 |
For second sample we need to define some extension functions. We don’t need linq library to make things work though. Let’s do it generic way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
template <typename T> T whereInList(const T& list, std::function<bool(decltype(list.at(0)))> f) { T result; for (auto& item : list) if (f(item)) result.push_back(item); return result; } template <typename T> T mapToList(const T& list, std::function<int(decltype(list.at(0)))> f) { T result; for (auto& item : list) result.push_back(f(item)); return result; } template <typename T> T logList(const T& list) { std::cout << "List: "; for (auto& item : list) std::cout << item << " "; std::cout << std::endl; return list; } template <typename F> auto piped1Arg(F f) -> decltype(piped(std::bind(f, placeholders::_1))) { return piped(std::bind(f, placeholders::_1)); } template <typename F> auto piped2Args(F f) -> decltype(piped(std::bind(f, placeholders::_1, placeholders::_2))) { return piped(std::bind(f, placeholders::_1, placeholders::_2)); } |
Processing functions here are just example and not so suitable for large amount of data – but whole concept is still viable.
Last two functions are used to create pipable functors from static methods:
1 2 3 4 5 6 7 |
vector<int> numbers{4,8,15,16,23,42}; using setType = decltype(numbers); auto where = piped2Args(whereInList<setType>); auto map = piped2Args(mapToList<setType>); auto log = piped1Arg(logList<setType>); auto result = numbers | where([](int x){ return (x > 10); }) | map([](int x){ return x + 5; }) | log(); |
As you can see the concept requires very compact implementation. If you somehow applied this in real project plz write in comments.