Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
variadic zip and join function extensions to boost range
#include <iostream>
#include <array>
#include <vector>
#include <list>
#include <deque>
#include "tupleit.hh" // http://pastebin.com/LFkTHdQk
#include <stdio.h>
#include <boost/range.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/join.hpp>
#include <boost/tuple/tuple_io.hpp>
/// Some parts of this code can be implemented with minor extensions to boost range.
/// A variadic join might look like this:
template<class C>
auto join(C&& c) -> decltype(boost::make_iterator_range(c)) {
return boost::make_iterator_range(c);
}
template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype(boost::join(boost::join(boost::make_iterator_range(std::forward<C>(c)),
boost::make_iterator_range(std::forward<D>(d))),
join(std::forward<Args>(args)...))) {
return boost::join(boost::join(boost::make_iterator_range(std::forward<C>(c)),
boost::make_iterator_range(std::forward<D>(d))),
join(std::forward<Args>(args)...));
}
/// A variadic zip is more complicated. If we want write access we cannot use
/// boost zip_iterator. Still one can use Anthony Williams' TupleIterator:
template <class... T>
auto zip(T&&... c)
-> boost::iterator_range<
decltype(iterators::makeTupleIterator(std::begin(std::forward<T>(c))...))> {
return boost::make_iterator_range
(iterators::makeTupleIterator(std::begin(std::forward<T>(c))...),
iterators::makeTupleIterator(std::end(std::forward<T>(c))...));
}
/// For read only access one could use boost::zip_iterator's but in my opinion
/// that is just not good enough.
////////////////////////////////////////////////////////////////////////////////
/// Part 2 implementation:
/// The following implements the negation operator for range filters:
namespace boost {
namespace range_detail {
// template <typename T>
// auto operator!(filter_holder<T> const& f)
// -> decltype(adaptors::filtered(std::not1(f.val))) {
// return adaptors::filtered(std::not1(f.val));
// }
template <typename T>
auto operator!(filter_holder<T> const& f)
-> decltype(adaptors::filtered(std::not1(f.val))) {
return adaptors::filtered(std::not1(f.val));
}
}
}
void part_one() {
/// Hi,
/// I would like to be able to write the following code using boost range:
std::array<double,4> a = {{ 1, 2, 3, 4 }};
std::list<int> b = { 11, 22, 33, 44 };
std::deque<int> c = { 111, 222, 333, 444 };
std::vector<int> d = {1111,2222,3333,4444 };
/// First if the values stored in a container have the same type, I
/// would like to iterate over different containers as if it were a single one, and:
/// - modify its value
for(auto&& i : join(b,c,d)) { i += 1; }
/// - use it with boost algorithms
boost::transform(join(b,c,d),begin(join(b,c,d)),[&](int j){ return j * 2; });
/// - and read it
for(const auto& i : join(b,c,d)) { std::cout << i << "\n"; }
/// I think this code is really easier to write than the corresponding stl iterator code.
/// A problem I usually face is manipulating data that has diferent types. This data
/// is usually stored in different containers for efficiency reasons, but it does
/// belong together. A way to iterate through this data is by means of the zip function:
for(const auto& t : zip(a,b,c,d)) { std::cout << t << "\n"; }
/// However, boost zip_iterators are not writable. I don't really think it would be possible
// to make them writtable, but that would allow code like this:
typedef decltype(*begin(zip(a,c,d))) tIt;
boost::sort(zip(a, c, d), [](const tIt& i, const tIt& j){
return boost::get<0>(i) > boost::get<0>(j); });
/// which sorts the three containers in lock-step after the values in container a:
for(auto&& t : zip(a, c, d)) { std::cout << t << std::endl; }
}
void part_two() {
/// When one uses ranges for a while, filtered is something really appealing:
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(auto i : v | boost::adaptors::filtered([](int i){ return i % 2; })) {
std::cout << i << "\n";
}
/// Quiet handy, but in real applications some filters are just used over and
/// over again, so one might be tempted to store them somehow:
/// - in a variable
auto not_4 = boost::adaptors::filtered([](int i){ return i != 4; });
/// - or in a function
auto feven = [](){ return boost::adaptors::filtered([](int i){ return i % 2 == 0; }); };
for(auto i : v | not_4 | feven()) { std::cout << i << "\n"; }
/// However say I now need a filter for odd numbers. I could of course define
/// od filter, but I would rather use the negation operator on a filter:
//for(auto i : v | !feven()) { std::cout << i << "\n"; } // doesn't work
/// but this doesn't work because we constructed the filters using a lambda!
/// That is if we want this functionality, right now the only way is to use
/// a std::function instead:
auto mfeven = boost::adaptors::filtered
(std::function<bool(int)>([](int i){ return i % 2 == 0; }));
for(auto i : v | !mfeven) { std::cout << i << "\n"; }
/// So this feels weird. I guess it would be nice if filtered supported lambdas.
/// My last point about filters is about the size of the filtered range. This
// feels weird:
std::cout << "# of even numbers in vector: "
<< boost::count_if(v | feven(), [](int){return true;}) << "\n";
/// Is there a better way of counting the elements of a range (eagerly)?
// count(range);
}
int main() {
part_one();
part_two();
return 0;
}
/// Thanks to:
/// everyone who participated in the so discussions):
// - join http://stackoverflow.com/questions/14366576/boostrangejoin-for-multiple-ranges
// - zip http://stackoverflow.com/questions/13840998/sorting-zipped-locked-containers-in-c-using-boost-or-the-stl
// - negation operator for filters http://stackoverflow.com/questions/14769418/negate-boost-range-filtered-adaptor
// Anthony Williams for tupleIterator and for pairign of iterators (http://www.justsoftwaresolutions.co.uk/articles/pair_iterators.pdf)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment