Grüß euch
Ich bin gerade dabei meine ersten Zeilen "Ranges" Code zu schreiben.
Konkret interessiert mich wie man eigene Views erzeugt und diese mit
anderen Algorithmen kombinierbar macht. Als Beispiel hab ich mir dafür
einen "toupper_view" überlegt, der eine Range von Buchstaben nimmt und
diese als Großbuchstaben wieder ausspuckt.
Soweit sogut. Die erste Version die ich geschrieben hab funktioniert
auch wunderbar. Es gibt einen
- toupper_view
sowie einen nested
- toupper_view::iterator
Um möglichst wenig Code zu duplizieren hab ich in der ersten Fassung vom
Iterator Typen der übergebenen Range abgeleitet. Das ist im Regelfall
der __normal_iterator aus der Standardbibliothek.
Hier der Vollständigkeit halber mal der Iterator Code:
1 | template <typename Rng>
|
2 | struct toupper_view<Rng>::iterator : ranges::iterator_t<Rng> {
|
3 | using base = ranges::iterator_t<Rng>;
|
4 | using value_type = typename ranges::range_value_t<Rng>;
|
5 |
|
6 | iterator() = default;
|
7 |
|
8 | iterator(base const& b) : base{b} {}
|
9 |
|
10 | iterator operator++(int) { return static_cast<base&>(*this)++; }
|
11 |
|
12 | iterator& operator++() {
|
13 | ++static_cast<base&>(*this);
|
14 | return (*this);
|
15 | }
|
16 |
|
17 | value_type operator*() const {
|
18 | return std::toupper(*static_cast<base>(*this));
|
19 | }
|
20 | };
|
Nun wollte ich zum Üben die Basisklasse aus der Standardbibliothek
loswerden und hab einen zweiten Iterator ohne Abhängigkeiten
geschrieben. Jener Iterator arbeitet jedoch nicht mit Pointern sondern
nimmt die gesamte Range sowie eine Größe als Eingangsparameter.
1 | template <typename Rng>
|
2 | struct toupper_view<Rng>::iterator {
|
3 | using value_type = typename ranges::range_value_t<Rng>;
|
4 |
|
5 | iterator() = default;
|
6 |
|
7 | iterator(Rng rng, size_t count) : rng_{rng}, count_{count} {}
|
8 |
|
9 | iterator operator++(int) {
|
10 | iterator const retval{*this};
|
11 | ++count_;
|
12 | return retval;
|
13 | }
|
14 |
|
15 | iterator& operator++() {
|
16 | ++count_;
|
17 | return *this;
|
18 | }
|
19 |
|
20 | value_type operator*() const { return std::toupper(rng_[count_]); }
|
21 |
|
22 | bool operator==(iterator const& rhs) const { return count_ == rhs.count_; }
|
23 |
|
24 | bool operator!=(iterator const& rhs) const { return count_ != rhs.count_; }
|
25 |
|
26 | private:
|
27 | Rng rng_{};
|
28 | size_t count_{};
|
29 | };
|
Leider funktioniert der zweite Iterator nur teilweise. Er tut zwar
prinzipiell wie gewollt, lässt sich jedoch nicht mit anderen
Range-Algorithmen kombinieren. Er besteht auch weder das Range-, noch
das View-Concept (siehe https://en.cppreference.com/w/cpp/ranges)...
Konkret dürfte es ihn hierbei aufstellen:
1 | template< class T >
|
2 | concept __RangeImpl = requires(T&& t) {
|
3 | ranges::begin(std::forward<T>(t)); // equality-preserving for forward iterators
|
4 | ranges::end (std::forward<T>(t));
|
5 | };
|
6 |
|
7 | // ->
|
8 | // begin(static_cast<T&&>(t))' would be ill-formed
|
9 | // end(static_cast<T&&>(t))' would be ill-formed
|
Ich versteh nur leider überhaupt nicht wieso?
Hier ist der gesamte Code auf godbolt zu finden:
https://godbolt.org/z/BZe6W9
Mit dem Macro "USE_ITERATOR" lässt sich zwischen den beiden Iteratoren
umschalten.