Is there any way to receive any iterable container as a parameter?
I would like a function to be able to receive any container while being able to begin() and end() on it. Unless I am wrong, every container should have these functions (std::view does not?).
Specifically, is there a way to receive a Container as an argument?
C++20 and/or tempates are fine.
void do_stuff(any_iterable_collection<int> &coll) {
for (auto it = coll.begin() ; it != coll.end() ; ++it) {
// do stuff with *it
}
}
std::list<int> list;
std::vector<int> vector;
std::set<int> set;
do_stuff(list);
do_stuff(vector);
do_stuff(set);
You can simply use the same way the standard perform this action:
template <typename Iterator>
void do_stuff(Iterator first, Iterator last) {
for (auto it = first; it != last; it = std::next(it)) {
// do stuff with *it
}
}
int main() {
std::vector<int> vec = {1, 5, 9};
do_stuff(vec.begin(), vec.end());
return EXIT_SUCCESS;
}
If you insist on the container:
template <template<typename> class Container>
void do_stuff(Container<int> &container) {
for (auto it = std::begin(container); it != std::end(container); it = std::next(it)) {
// do stuff with *it
std::cout << *it << std::endl;
}
}
Or for more generally container:
template <template<typename> class Container, typename CType>
void do_stuff(Container<CType> &container) {
for (auto it = std::begin(container); it != std::end(container); it = std::next(it)) {
// do stuff with *it
std::cout << *it << std::endl;
}
}
A simplified c++20 concept based on your requirements:
#include <concepts>
template <typename C, typename T>
concept any_iterable_collection =
std::same_as<typename C::value_type, T> &&
requires (C c) {
{ c.begin() } -> std::forward_iterator;
{ c.end() } -> std::forward_iterator;
{ const_cast<const C&>(c).begin() } -> std::forward_iterator;
{ const_cast<const C&>(c).end() } -> std::forward_iterator;
};
Usage:
void do_stuff(const any_iterable_collection<int> auto& coll);
DEMO
Related
I have two structures with methods returning iterators on begin and end of object collection they own. Methods have different names (this may seem like a bad app architecture, but this is only a simplified model):
struct A {
std::vector<int>::iterator a_begin() { return v.begin(); }
std::vector<int>::iterator a_end() { return v.end(); }
std::vector<int> v = { 1, 2 };
};
struct B {
std::vector<float>::iterator b_begin() { return v.begin(); }
std::vector<float>::iterator b_end() { return v.end(); }
std::vector<float> v = { 1.0f, 2.0f };
};
I want to write a template function which will iterate over given object (of type A or type B) and do some job with its elements. My approach is:
template<class T>
void foo(T t) {
if constexpr (std::is_same_v<T, A>) {
for (auto it = t.a_begin(); it != t.a_end(); it++) {
// a lot of stuff
}
} else if constexpr (std::is_same_v<T, B>) {
for (auto it = t.b_begin(); it != t.b_end(); it++) {
// the same stuff
}
}
}
It looks a bit ugly for me because of for loops bodies are the same. Is there any way to improve this?
I'm taking your claim about naming and complexity at face value, so abstract and bridge.
namespace detail {
inline auto foo_begin(A& a) { return a.a_begin(); }
inline auto foo_end (A& a) { return a.a_end(); }
inline auto foo_begin(B& b) { return b.b_begin(); }
inline auto foo_end (B& b) { return b.b_end(); }
}
template<class T>
void foo(T t) {
for (auto it = detail::foo_begin(t); it != detail::foo_end(t); ++it) {
// the same stuff
}
}
The operation you wanted to vary is the range selection. So a small overload set for the types you care about should do it nicely.
If you do this often, a range adapter may be worthwhile to consider. You can write it by hand, or with C++20's std::ranges::subrange you may even leverage this overload set itself.
template<class T>
void foo(T t) {
for (auto &item : std::ranges::subrange(detail::foo_begin(t), detail::foo_end(t))) {
// the same stuff
}
}
The key concept for iterators is that two iterators define a sequence. That's all there is to it: just use a pair of iterators, rather than a container:
template <class It>
void foo(It begin, It end) {
while (begin != end) {
// a lot of stuff
++begin;
}
}
Now you can call it with a range defined by any kind of container you like:
A a;
foo(a.a_begin(), a.a_end());
B b;
foo(b.b_begin(), b.b_end());
Maybe this helps to rethink your problem.
#include <vector>
#include <algorithm>
template<typename T>
void DoStuff(const T& value)
{
};
template<typename T>
void DoAllStuffFor(const std::vector<T>& v)
{
std::for_each(v.begin(), v.end(), DoStuff<T>);
}
int main()
{
std::vector<int> v1 = { 1, 2 };
std::vector<double> v2 = { 1, 2 };
DoAllStuffFor(v1);
DoAllStuffFor(v2);
}
I am wondering if C++20 ranges have some nice way for me to iterate over the equal ranges of sorted container(or in general case any sorted range).
I have this "manual" solution that works, but it is not nice in a sense that it is not really composable (I do not get some view of equal range views back, I just provide function to be called).
#include <vector>
#include <ranges>
#include <iostream>
#include <fmt/ranges.h>
template<typename Cont, typename Fn>
void for_each_equal_range(const Cont& cont, Fn fn){
auto current_begin = cont.begin();
while(true){
if (current_begin==cont.end()){
return;
}
auto [eq_begin, eq_end] = std::equal_range(current_begin, cont.end(), *current_begin);
fn(eq_begin, eq_end);
current_begin = eq_end;
}
}
int main() {
std::vector vals {1,2,2,3,3,3,47};
for_each_equal_range(vals, [](const auto b, const auto e){
std::cout << "size: " << std::distance(b,e) << std::endl;
std::ranges::subrange elems(b, e);
std::cout << fmt::format("{}",elems) << std::endl;
});
}
I wish I had something like:
vals | equal_range_split | std::ranges::for_each(...);
In case there is some confusion wrt what range means here:
equal range is good old STL equal_range meaning
ranges is C++20 ranges library.
Also I know C++20 has std::ranges::equal_range algorithm, but it seems to be not that helpful for my use case.
Not quite what you are looking for, but if you have a coroutine generator template, something like
using namespace std::ranges;
template<typename Range, typename Compare = std::less>
generator<subrange<iterator_t<Range>>> equal_ranges(Range&& range, Compare compare = {}) {
for (auto current_begin = cont.begin(); current_begin != cont.end();) {
auto [eq_begin, eq_end] = std::equal_range(current_begin, cont.end(), *current_begin, compare);
co_yield { eq_begin, eq_end };
current_begin = eq_end;
}
}
template<typename Compare>
struct equal_ranges_holder {
Compare compare;
};
template<typename Compare = std::less>
equal_ranges_holder<Compare> equal_ranges(Compare compare = {}) { return { compare }; }
template<typename Range, typename Compare>
auto operator|(Range&& range, equal_ranges_holder<Compare> holder) {
return equal_ranges(range, holder.compare);
}
Alternatively, it's fairly simple to make a single pass equal_range_view.
using namespace std::ranges;
template<borrowed_range Range, std::strict_weak_order Compare = std::less>
class equal_range_view : public view_interface<equal_range_view> {
using base_iterator = iterator_t<Range>;
using base_sentinel = sentinel_t<Range>;
Range range;
Compare compare;
public:
class sentinel {};
class iterator {
base_iterator base;
base_sentinel end;
Compare compare;
public:
using value_type = subrange<base_iterator>;
using reference = value_type;
using pointer = value_type *;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
equal_range_iterator(base_iterator base, base_sentinel end, Compare compare) : base(base), end(end), compare(compare) {}
reference operator*() {
auto [first, last] = std::equal_range(base, end, compare);
return { first, last };
}
iterator& operator++() {
auto [_, last] = std::equal_range(base, end, compare);
base = last;
return *this;
}
bool operator==(sentinel) const {
return base == end;
}
bool operator!=(sentinel) const {
return base != end;
}
};
explicit equal_range_view(Range range, Compare compare = {}) : range(range), compare(compare) {}
iterator begin() { return { range.begin(), range.end(), compare }; }
sentinel end() { return {}; }
};
In C++20, pipe operator has not defined for std::ranges::equal_range and std::ranges::for_each yet. So simple technique to implement above code is like:
for (int global = *vals.begin() - 1 ;
auto val : vals | std::ranges::views::filter([&](auto value){return value != global ;}))
{
std::ranges::for_each(std::ranges::equal_range(vals, val), [](auto& x){ std::cout << x << std::endl; });
global = val;
}
I'm trying to write a generic erase_if method that can be used on any container type to remove elements given a predicate. It should either use the erase-remove-idiom if the container allows it, or loop through the container and call the erase method. I also just want to provide the container itself, not the begin and end iterator separatly. That shall be handled by the method.
However I can't get the meta-template to work to distinguish between the two cases through SFINAE. I'm trying to check whether the method std::remove_if (or std::remove) would be well defined for a given type, but the value is either true for both vector and map (in which case the code won't compile) or false for both of them. I'm quite new to template meta-programming, so is there something I'm missing? Or maybe there's another, better solution?
Below is my example code:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <type_traits>
#include <vector>
namespace my_std
{
using std::begin;
using std::end;
namespace detail
{
template <typename T>
// this is false for both vector and map
auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
[](auto&&) { return false; }),
std::true_type{});
// this is true for both vector and map
// auto is_remove_compatible(int)
// -> decltype(std::remove(begin(std::declval<T&>()), end(std::declval<T&>()), std::declval<T::value_type&>()),
// std::true_type{});
template <typename T>
auto is_remove_compatible(...) -> std::false_type;
}
template <typename T>
using is_remove_compatible = decltype(detail::is_remove_compatible<T>(0));
template <typename T>
constexpr bool is_remove_compatible_v = is_remove_compatible<T>::value;
template <typename Cont, typename Pred>
std::enable_if_t<is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
{
std::cout << "Using erase-remove\n";
container.erase(std::remove_if(begin(container), end(container), pred), end(container));
}
template <typename Cont, typename Pred>
std::enable_if_t<!is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
{
std::cout << "Using loop\n";
for (auto it = begin(container); it != end(container);)
{
if (pred(*it))
it = container.erase(it);
else
++it;
}
}
}
template <typename T>
std::ostream& operator<<(std::ostream& out, std::vector<T> const& v)
{
if (!v.empty())
{
out << '[';
std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
out << "\b\b]";
}
return out;
}
template <typename K, typename V>
std::ostream& operator<<(std::ostream& out, std::map<K, V> const& v)
{
out << '[';
for (auto const& p : v)
out << '{' << p.first << ", " << p.second << "}, ";
out << "\b\b]";
return out;
}
int main(int argc, int argv[])
{
auto vp = my_std::is_remove_compatible_v<std::vector<int>>;
auto mp = my_std::is_remove_compatible_v<std::map<int, int>>;
std::cout << vp << ' ' << mp << '\n';
std::vector<int> v = {1, 2, 3, 4, 5};
auto v2 = v;
my_std::erase_if(v2, [](auto&& x) { return x % 2 == 0; });
std::map<int, int> m{{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}};
auto m2 = m;
my_std::erase_if(m2, [](auto&& x) { return x.first % 2 == 0; });
std::cout << v << " || " << v2 << '\n';
std::cout << m << " || " << m2 << '\n';
std::cin.ignore();
}
This idea is wrong for two reasons:
auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
[](auto&&) { return false; }),
std::true_type{});
First, you can't have a lambda in an unevaluated context, so the code is ill-formed. Second, even if it wasn't ill-formed (which is easy to fix), remove_if() isn't SFINAE-friendly on anything. If you pass in something that isn't a predicate to remove_if, or a non-modifiable iterator, that's not required by the standard to be anything but a hard error.
The way I would do it right now is via the chooser idiom. Basically, have conditional sfinae based on a rank ordering:
template <std::size_t I> struct chooser : chooser<I-1> { };
template <> struct chooser<0> { };
template <class Cont, class Predicate>
void erase_if(Cont& container, Predicate&& predicate) {
return erase_if_impl(container, std::forward<Predicate>(predicate), chooser<10>{});
}
And then we just have our various erase_if_impl conditions, in order:
// for std::list/std::forward_list
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<3> )
-> decltype(void(c.remove_if(std::forward<Predicate>(predicate))))
{
c.remove_if(std::forward<Predicate>(predicate));
}
// for the associative containers (set, map, ... )
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<2> )
-> decltype(void(c.find(std::declval<typename Cont::key_type const&>())))
{
using std::begin; using std::end;
for (auto it = begin(c); it != end(c); )
{
if (predicate(*it)) {
it = c.erase(it);
} else {
++it;
}
}
}
// for everything else, there's MasterCard
template <class Cont, class Predicate>
void erase_if_impl(Cont& c, Predicate&& predicate, chooser<1> )
{
using std::begin; using std::end;
c.erase(std::remove_if(begin(c), end(c), std::forward<Predicate>(predicate)), end(c));
}
I would like to achieve something like this:
template<class IT>
size_t foo(IT begin,IT end) {return end-begin;}
template<template (class) class FOO>
class BAR
{
public:
any_container<any_type> container;
size_t call_foo
{
FOO<any_container<any_type>::iterator>(container.begin(), container.end());
}
};
Moreover, I want to be able to pass function, lambda or function object as FOO.
Probably std::function should be used here but it is not possible to declare std::function<size_t(T,T)> with arbitrary type T.
And I definitely don't want to specify type of inner container or its iterator on template BAR argument list.
Is there any way to solve this in elegant way?
Based on your comment I think you are looking for something like this:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>
struct Sender {
template<typename Iter>
std::size_t operator()(Iter begin, Iter end) {
for(Iter cur = begin; cur != end; ++cur) {
// your low-level send stuff
}
return (end - begin);
}
};
struct Receiver {
template<typename Iter>
std::size_t operator()(Iter begin, Iter end) {
for(Iter cur = begin; cur != end; ++cur) {
// your low-level receive stuff
}
return (end - begin);
}
};
template<typename Fn>
struct Bar
{
protected:
Fn _fn;
public:
Bar(Fn fn) : _fn(std::move(fn)) { }
template<typename Container>
std::size_t call_fn(Container & container)
{
return _fn(std::begin(container), std::end(container));
}
};
template<typename Fn>
auto create_bar(Fn && fn) -> Bar<typename std::remove_reference<Fn>::type> {
return { std::forward<Fn>(fn) };
}
Usage is simple:
template<typename Iter>
std::size_t my_foo(Iter begin, Iter end) {
return (end - begin);
}
int main(int argc, char ** argv) {
typedef typename std::vector<int>::iterator iter;
// functors
auto functor = create_bar(Sender());
// lambdas
auto lambda = create_bar([](iter begin, iter end) -> std::size_t { return (end - begin); });
// plain old functions
auto function = create_bar(&my_foo<iter>);
std::vector<int> tmp = { 0, 1, 2, 5};
std::cout << "Functor: " << functor.call_fn(tmp) << std::endl;
std::cout << "Lambda: " << lambda.call_fn(tmp) << std::endl;
std::cout << "Function: " << function.call_fn(tmp) << std::endl;
return 0;
}
Live example here
Is there any existing iterator implementation (perhaps in boost) which implement some sort of flattening iterator?
For example:
unordered_set<vector<int> > s;
s.insert(vector<int>());
s.insert({1,2,3,4,5});
s.insert({6,7,8});
s.insert({9,10,11,12});
flattening_iterator<unordered_set<vector<int> >::iterator> it( ... ), end( ... );
for(; it != end; ++it)
{
cout << *it << endl;
}
//would print the numbers 1 through 12
I don't know of any implementation in a major library, but it looked like an interesting problem so I wrote a basic implementation. I've only tested it with the test case I present here, so I don't recommend using it without further testing.
The problem is a bit trickier than it looks because some of the "inner" containers may be empty and you have to skip over them. This means that advancing the flattening_iterator by one position may actually advance the iterator into the "outer" container by more than one position. Because of this, the flattening_iterator needs to know where the end of the outer range is so that it knows when it needs to stop.
This implementation is a forward iterator. A bidirectional iterator would also need to keep track of the beginning of the outer range. The flatten function templates are used to make constructing flattening_iterators a bit easier.
#include <iterator>
// A forward iterator that "flattens" a container of containers. For example,
// a vector<vector<int>> containing { { 1, 2, 3 }, { 4, 5, 6 } } is iterated as
// a single range, { 1, 2, 3, 4, 5, 6 }.
template <typename OuterIterator>
class flattening_iterator
{
public:
typedef OuterIterator outer_iterator;
typedef typename OuterIterator::value_type::iterator inner_iterator;
typedef std::forward_iterator_tag iterator_category;
typedef typename inner_iterator::value_type value_type;
typedef typename inner_iterator::difference_type difference_type;
typedef typename inner_iterator::pointer pointer;
typedef typename inner_iterator::reference reference;
flattening_iterator() { }
flattening_iterator(outer_iterator it) : outer_it_(it), outer_end_(it) { }
flattening_iterator(outer_iterator it, outer_iterator end)
: outer_it_(it),
outer_end_(end)
{
if (outer_it_ == outer_end_) { return; }
inner_it_ = outer_it_->begin();
advance_past_empty_inner_containers();
}
reference operator*() const { return *inner_it_; }
pointer operator->() const { return &*inner_it_; }
flattening_iterator& operator++()
{
++inner_it_;
if (inner_it_ == outer_it_->end())
advance_past_empty_inner_containers();
return *this;
}
flattening_iterator operator++(int)
{
flattening_iterator it(*this);
++*this;
return it;
}
friend bool operator==(const flattening_iterator& a,
const flattening_iterator& b)
{
if (a.outer_it_ != b.outer_it_)
return false;
if (a.outer_it_ != a.outer_end_ &&
b.outer_it_ != b.outer_end_ &&
a.inner_it_ != b.inner_it_)
return false;
return true;
}
friend bool operator!=(const flattening_iterator& a,
const flattening_iterator& b)
{
return !(a == b);
}
private:
void advance_past_empty_inner_containers()
{
while (outer_it_ != outer_end_ && inner_it_ == outer_it_->end())
{
++outer_it_;
if (outer_it_ != outer_end_)
inner_it_ = outer_it_->begin();
}
}
outer_iterator outer_it_;
outer_iterator outer_end_;
inner_iterator inner_it_;
};
template <typename Iterator>
flattening_iterator<Iterator> flatten(Iterator it)
{
return flattening_iterator<Iterator>(it, it);
}
template <typename Iterator>
flattening_iterator<Iterator> flatten(Iterator first, Iterator last)
{
return flattening_iterator<Iterator>(first, last);
}
The following is a minimal test stub:
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
int main()
{
// Generate some test data: it looks like this:
// { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } }
std::vector<std::vector<int>> v(3);
int i(0);
for (auto it(v.begin()); it != v.end(); ++it)
{
it->push_back(i++); it->push_back(i++);
it->push_back(i++); it->push_back(i++);
}
// Flatten the data and print all the elements:
for (auto it(flatten(v.begin(), v.end())); it != v.end(); ++it)
{
std::cout << *it << ", ";
}
std::cout << "\n";
// Or, since the standard library algorithms are awesome:
std::copy(flatten(v.begin(), v.end()), flatten(v.end()),
std::ostream_iterator<int>(std::cout, ", "));
}
Like I said at the beginning, I haven't tested this thoroughly. Let me know if you find any bugs and I'll be happy to correct them.
I decided to "improve" a bit on the flattening iterator concept, though as noted by James you are stuck using Ranges (except for the inner most container), so I just used ranges through and through and thus obtained a flattened range, with an arbitrary depth.
First I used a building brick:
template <typename C>
struct iterator { using type = typename C::iterator; };
template <typename C>
struct iterator<C const> { using type = typename C::const_iterator; };
And then defined a (very minimal) ForwardRange concept:
template <typename C>
class ForwardRange {
using Iter = typename iterator<C>::type;
public:
using pointer = typename std::iterator_traits<Iter>::pointer;
using reference = typename std::iterator_traits<Iter>::reference;
using value_type = typename std::iterator_traits<Iter>::value_type;
ForwardRange(): _begin(), _end() {}
explicit ForwardRange(C& c): _begin(begin(c)), _end(end(c)) {}
// Observers
explicit operator bool() const { return _begin != _end; }
reference operator*() const { assert(*this); return *_begin; }
pointer operator->() const { assert(*this); return &*_begin; }
// Modifiers
ForwardRange& operator++() { assert(*this); ++_begin; return *this; }
ForwardRange operator++(int) { ForwardRange tmp(*this); ++*this; return tmp; }
private:
Iter _begin;
Iter _end;
}; // class ForwardRange
This is our building brick here, though in fact we could make do with just the rest:
template <typename C, size_t N>
class FlattenedForwardRange {
using Iter = typename iterator<C>::type;
using Inner = FlattenedForwardRange<typename std::iterator_traits<Iter>::value_type, N-1>;
public:
using pointer = typename Inner::pointer;
using reference = typename Inner::reference;
using value_type = typename Inner::value_type;
FlattenedForwardRange(): _outer(), _inner() {}
explicit FlattenedForwardRange(C& outer): _outer(outer), _inner() {
if (not _outer) { return; }
_inner = Inner{*_outer};
this->advance();
}
// Observers
explicit operator bool() const { return static_cast<bool>(_outer); }
reference operator*() const { assert(*this); return *_inner; }
pointer operator->() const { assert(*this); return _inner.operator->(); }
// Modifiers
FlattenedForwardRange& operator++() { ++_inner; this->advance(); return *this; }
FlattenedForwardRange operator++(int) { FlattenedForwardRange tmp(*this); ++*this; return tmp; }
private:
void advance() {
if (_inner) { return; }
for (++_outer; _outer; ++_outer) {
_inner = Inner{*_outer};
if (_inner) { return; }
}
_inner = Inner{};
}
ForwardRange<C> _outer;
Inner _inner;
}; // class FlattenedForwardRange
template <typename C>
class FlattenedForwardRange<C, 0> {
using Iter = typename iterator<C>::type;
public:
using pointer = typename std::iterator_traits<Iter>::pointer;
using reference = typename std::iterator_traits<Iter>::reference;
using value_type = typename std::iterator_traits<Iter>::value_type;
FlattenedForwardRange(): _range() {}
explicit FlattenedForwardRange(C& c): _range(c) {}
// Observers
explicit operator bool() const { return static_cast<bool>(_range); }
reference operator*() const { return *_range; }
pointer operator->() const { return _range.operator->(); }
// Modifiers
FlattenedForwardRange& operator++() { ++_range; return *this; }
FlattenedForwardRange operator++(int) { FlattenedForwardRange tmp(*this); ++*this; return tmp; }
private:
ForwardRange<C> _range;
}; // class FlattenedForwardRange
And apparently, it works
I arrive a little late here, but I have just published a library (multidim) to deal with such problem. The usage is quite simple: to use your example,
#include "multidim.hpp"
// ... create "s" as in your example ...
auto view = multidim::makeFlatView(s);
// view offers now a flattened view on s
// You can now use iterators...
for (auto it = begin(view); it != end(view); ++it) cout << *it << endl;
// or a simple range-for loop
for (auto value : view) cout << value;
The library is header-only and has no dependencies. Requires C++11 though.
you can make one using iterator facade in boost.
I wrote iterator product which you can use as a template perhaps:
http://code.google.com/p/asadchev/source/browse/trunk/work/cxx/iterator/product.hpp
In addition to the answer of Matthieu, you can automatically count the amount of dimensions of the iterable/container. But first we must set up a rule when something is an iterable/container:
template<class T, class R = void>
struct AliasWrapper {
using Type = R;
};
template<class T, class Enable = void>
struct HasValueType : std::false_type {};
template<class T>
struct HasValueType<T, typename AliasWrapper<typename T::value_type>::Type> : std::true_type {};
template<class T, class Enable = void>
struct HasConstIterator : std::false_type {};
template<class T>
struct HasConstIterator<T, typename AliasWrapper<typename T::const_iterator>::Type> : std::true_type {};
template<class T, class Enable = void>
struct HasIterator : std::false_type {};
template<class T>
struct HasIterator<T, typename AliasWrapper<typename T::iterator>::Type> : std::true_type {};
template<class T>
struct IsIterable {
static constexpr bool value = HasValueType<T>::value && HasConstIterator<T>::value && HasIterator<T>::value;
};
We can count the dimensions as follows:
template<class T, bool IsCont>
struct CountDimsHelper;
template<class T>
struct CountDimsHelper<T, true> {
using Inner = typename std::decay_t<T>::value_type;
static constexpr int value = 1 + CountDimsHelper<Inner, IsIterable<Inner>::value>::value;
};
template<class T>
struct CountDimsHelper<T, false> {
static constexpr int value = 0;
};
template<class T>
struct CountDims {
using Decayed = std::decay_t<T>;
static constexpr int value = CountDimsHelper<Decayed, IsIterable<Decayed>::value>::value;
};
We then can create a view wrapper, that contains a begin() and end() function.
template<class Iterable, int Dims>
class Flatten {
public:
using iterator = FlattenIterator<Iterable, Dims>;
private:
iterator _begin{};
iterator _end{};
public:
Flatten() = default;
template<class I>
explicit Flatten(I&& iterable) :
_begin(iterable),
_end(iterable)
{}
iterator begin() const {
return _begin;
}
iterator end() const {
return _end;
}
};
To make the creation of the object Flatten a bit easier, we define a helper function:
template<class Iterable>
Flatten<std::decay_t<Iterable>, CountDims<Iterable>::value - 1> flatten(Iterable&& iterable) {
return Flatten<std::decay_t<Iterable>, CountDims<Iterable>::value - 1>(iterable);
}
Usage:
std::vector<std::vector<int>> vecs = {{1,2,3}, {}, {4,5,6}};
for (int i : flatten(vecs)) {
// do something with i
}