The code below is my first attempt to create a concat
"range adapter" using C++20 ranges. It compiles and runs with the latest Clang. The little test function creates a few different types of ranges, and passes them to concat
to create a single concatenated view. It prints the result, as expected:
1 2 3 4 5 6 7 8 9
The idea of concat
is that it can take different types of ranges, as long as the element types are the same (or maybe convertible to one another).
I'm comfortable with some aspects of C++, but I'm more of a beginner with the template meta-programming and recent additions to the standard, like concepts and ranges. So I'm sure this code is far from an "industrial strength" version.
I'm interested in hearing suggestions about any fixes and improvements, and I'm specifically interested in some next steps to make it more like the high-quality range adapters that would appear in the standard library.
If the standard library has a better way to concatenate a bunch of ranges, then I'd like to know about that, but I also want to create this one, because I want to learn how to build my own range adapters and views like this.
In my real project, this will be wrapped in a C++20 module, but for simplicity here I pasted it all in one file and removed the import
and exports
.
#include <vector>
#include <list>
#include <span>
#include <iostream>
#include <string>
#include <ranges>
using std::vector;
using std::cout; using std::endl;
using std::string;
namespace r = std::ranges;
namespace v = std::views;
namespace my_views {
// For convenience, create a type-function that returns the first type of a parameter pack.
template <typename T, typename... Rest>
struct GetFirstTypeT {
using Type = T;
};
template <typename... Pack>
using GetFirstType = GetFirstTypeT<Pack...>::Type;
// The `ExpandedIter` type will be recursively instantiated to implement the `ConcatIter` for a
// concatenated view.
template <typename... Ranges>
struct ExpandedIter;
template <typename R, typename... Ranges>
struct ExpandedIter<R, Ranges...> {
using EltT = r::range_value_t<R>;
using IterT = r::iterator_t<R>;
R &r;
IterT iter;
ExpandedIter<Ranges...> rest_iter;
ExpandedIter(R &r, Ranges&... rest) :r(r), rest_iter(rest...) {
iter = r.begin();
}
bool is_end() const {
if (iter == r.end()) {
return rest_iter.is_end();
} else {
return false;
}
}
int current() {
if (iter == r.end()) {
return rest_iter.current();
} else {
return *iter;
}
}
void next() {
if (iter == r.end()) {
rest_iter.next();
} else {
++iter;
}
}
};
template <>
struct ExpandedIter<> {
bool is_end() const { return true; }
int current() { throw -111; }
void next() {}
};
template <typename... Views>
class ConcatIter {
ExpandedIter<Views...> exp_iter;
public:
using EltT = r::range_value_t<GetFirstType<Views...>>;
ConcatIter(Views&... views) :exp_iter(views...) { }
ConcatIter(const ConcatIter&) = delete;
ConcatIter(ConcatIter&& other) = default;
ConcatIter &operator=(const ConcatIter&) = delete;
ConcatIter &operator=(ConcatIter&&) = delete;
bool is_end() const {
return exp_iter.is_end();
}
EltT operator*() {
return exp_iter.current();
}
ConcatIter& operator++() {
exp_iter.next();
return *this;
}
private:
};
struct EndIter {
};
template <typename... Rs>
bool operator==(const ConcatIter<Rs...> &iter, EndIter) {
return iter.is_end();
}
template <typename... Ranges>
class ConcatView {
using IterT = ConcatIter<decltype(v::all(std::forward<Ranges>(std::declval<Ranges&>())))...>;
std::tuple<decltype(v::all(std::forward<Ranges>(std::declval<Ranges&>())))...> views;
public:
ConcatView(Ranges&&... ranges) :views(v::all(std::forward<Ranges>(ranges))...) {
}
IterT begin() {
return std::make_from_tuple<IterT>(views);
}
EndIter end() { return {}; };
private:
};
// Concatenate ranges with same (or compatible?) element type.
template <typename... Rs>
requires (r::range<Rs> && ...)
ConcatView<Rs...> concat(Rs&&... rs) {
ConcatView<Rs...> v(std::forward<Rs>(rs)...);
return v;
}
} // end my_views namespace
void test1() {
vector vec{1,2,3};
int ia[] {7,8,9};
std::span span{ia};
vector<int> empty_vec;
auto view = my_views::concat(vec, std::list{4,5,6}, empty_vec, span);
for (auto elt : view) {
cout << elt << ' ';
}
cout << endl;
}
int main(int argc, char *argv[])
{
test1();
return 0;
}
beginner
tag could be applied if desired \$\endgroup\$std::ranges::join_view
, but it's hard to use that to concatenate ranges of different types like this (you'd need to use an adapter to type-erase the constituent ranges). On the other hand, that permits concatenation of run-time-variable number of ranges, and could reduce the amount of code produced by instantiating templates. \$\endgroup\$