I have multiple boost::container::small_vector's stored in one std::vector and I want to return all values stored in the small_vectors as a boost::any_range. Have you any ideas how I can do that?
1 Answers
Sure you can do it in a very inefficient way without writing more code yourself:
return boost::accumulate(_storage, R {},
[](R&& accum, SV const& sv) { return boost::join(std::move(accum), sv); });
Full demo Live On Godbolt
#include <boost/container/small_vector.hpp>
#include <boost/range/any_range.hpp>
#include <boost/range/join.hpp>
#include <boost/range/numeric.hpp>
#include <fmt/ranges.h>
template <typename T>
struct MyData {
using SV = boost::container::small_vector<T, 10>;
using R = boost::any_range<T, boost::bidirectional_traversal_tag>;
R all_view() const {
return boost::accumulate(_storage, R {},
[](R&& accum, SV const& sv) { return boost::join(std::move(accum), sv); });
}
std::vector<SV> _storage;
};
int main() {
MyData<int> ints { { {1,2,3}, {4}, {5,6,7} } };
fmt::print("ints: {}\n", ints.all_view());
}
Prints
ints: {1, 2, 3, 4, 5, 6, 7}
Pros: quick to write
Cons:
- requires recent boost and compiler
- generates increasingly bad code for larger numbers of subranges: see e.g.
Beware of the simplicity: I imagine any_range works its magic using erasure, which means that you still (logically) have a sequence of ranges, except now it is likely stored in a recursive tree of range objects, each node potentially adding a virtual function call, and possibly dynamic allocation. (The virtual call might be optimized by a very smart compiler, and the dynamic allocation might use small-object-buffer optimization small ranges (as a QoI concern), but that still limits the composability of the ranges.)
Alternatives:
If you can, consider RangeV3 which has yield_from
which can flatten the ranges in an efficient way because it doesn't do type erasure.
Alternatively, you can write your own flattening iterator. This is more work but can be manageable with boost and will get you the best expectable performance.
#include <boost/container/small_vector.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/optional.hpp>
#include <fmt/ranges.h>
template <typename T>
struct MyData {
using SV = boost::container::small_vector<T, 10>;
std::vector<SV> _storage;
struct const_iterator : boost::iterator_facade<const_iterator, T const,
boost::forward_traversal_tag> {
using outer = typename std::vector<SV>::const_iterator;
using inner = typename SV::const_iterator;
const_iterator(const_iterator const&) = default;
const_iterator& operator=(const_iterator const&) = default;
const_iterator(outer f, outer l) : _outer(f), _last(l) {
if (_outer != _last)
_inner = _outer->begin();
forward_transport();
}
const_iterator(outer f = {}) : const_iterator(f, f) {}
bool equal(const_iterator const& other) const {
return _outer == other._outer && _inner == other._inner;
}
void increment() {
assert(_outer != _last && _inner != _outer->end());
++_inner;
forward_transport();
}
decltype(auto) dereference() const {
assert(_outer != _last);
return *_inner;
}
protected:
void forward_transport() { // skip over empty subrange(s)
while (_outer != _last && _inner == _outer->end()) {
++_outer;
if (_outer == _last)
_inner = {};
else
_inner = _outer->begin();
}
}
outer _outer, _last;
inner _inner = {};
};
const_iterator begin() const { return {_storage.begin(), _storage.end()}; }
const_iterator end() const { return {_storage.end(), _storage.end()}; }
};
int main() {
MyData<int> ints { { {}, {}, {}, { 1, 2, 3 }, {}, {}, {}, {}, { 4 }, { 5, 6, 7 }, {} } };
fmt::print("ints: {}\n", ints);
}
Prints (again/still):
ints: {1, 2, 3, 4, 5, 6, 7}

- 374,641
- 47
- 450
- 633
-
Added a manual iterator demo https://godbolt.org/z/dfMKc3 – sehe Feb 22 '21 at 23:57