diff --git a/src/json.hpp b/src/json.hpp index d79fb841..2a4873b4 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -5239,18 +5239,95 @@ class basic_json internal_iterator m_it = internal_iterator(); }; - /// a reverse random access iterator for the basic_json class + /*! + @brief a reverse random access iterator for the basic_json class + + The reverse iterator is realized with the `std::reverse_iterator` adaptor. + This adaptor does not automatically inherit all functionality from the + base iterator class, so some functions need to be explicitly implemented + by either delegating them to the base class or by using the `base()` + function to access the underlying base iterator. + + The following operators are implicitly inherited: + + - `operator==`, `operator!=`, `operator<`, `operator<=`, `operator>`, + `operator>=` + - `operator-=` + - `operator->`, `operator*` + */ class reverse_iterator : public std::reverse_iterator { public: - reverse_iterator(const typename - std::reverse_iterator::iterator_type& - it) - : std::reverse_iterator(it) {} + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; - reverse_iterator(const std::reverse_iterator& it) - : std::reverse_iterator(it) - {} + /// create reverse iterator from iterator + reverse_iterator(const typename base_iterator::iterator_type& it) + : base_iterator(it) {} + + /// create reverse iterator from base class + reverse_iterator(const base_iterator& it) : base_iterator(it) {} + + /// post-increment (it++) + reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } /// return the key of an object iterator typename object_t::key_type key() const @@ -5271,9 +5348,76 @@ class basic_json class const_reverse_iterator : public std::reverse_iterator { public: - const_reverse_iterator(const typename - std::reverse_iterator::iterator_type& it) - : std::reverse_iterator(it) {} + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; + + /// create reverse iterator from iterator + const_reverse_iterator(const typename base_iterator::iterator_type& it) + : base_iterator(it) {} + + /// create reverse iterator from base class + const_reverse_iterator(const base_iterator& it) : base_iterator(it) {} + + /// post-increment (it++) + const_reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + const_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + const_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + const_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + const_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + const_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + const_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const const_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + const_reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } /// return the key of an object iterator typename object_t::key_type key() const diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index a8fefe61..4258015f 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -5239,18 +5239,95 @@ class basic_json internal_iterator m_it = internal_iterator(); }; - /// a reverse random access iterator for the basic_json class + /*! + @brief a reverse random access iterator for the basic_json class + + The reverse iterator is realized with the `std::reverse_iterator` adaptor. + This adaptor does not automatically inherit all functionality from the + base iterator class, so some functions need to be explicitly implemented + by either delegating them to the base class or by using the `base()` + function to access the underlying base iterator. + + The following operators are implicitly inherited: + + - `operator==`, `operator!=`, `operator<`, `operator<=`, `operator>`, + `operator>=` + - `operator-=` + - `operator->`, `operator*` + */ class reverse_iterator : public std::reverse_iterator { public: - reverse_iterator(const typename - std::reverse_iterator::iterator_type& - it) - : std::reverse_iterator(it) {} + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; - reverse_iterator(const std::reverse_iterator& it) - : std::reverse_iterator(it) - {} + /// create reverse iterator from iterator + reverse_iterator(const typename base_iterator::iterator_type& it) + : base_iterator(it) {} + + /// create reverse iterator from base class + reverse_iterator(const base_iterator& it) : base_iterator(it) {} + + /// post-increment (it++) + reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } /// return the key of an object iterator typename object_t::key_type key() const @@ -5271,9 +5348,76 @@ class basic_json class const_reverse_iterator : public std::reverse_iterator { public: - const_reverse_iterator(const typename - std::reverse_iterator::iterator_type& it) - : std::reverse_iterator(it) {} + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; + + /// create reverse iterator from iterator + const_reverse_iterator(const typename base_iterator::iterator_type& it) + : base_iterator(it) {} + + /// create reverse iterator from base class + const_reverse_iterator(const base_iterator& it) : base_iterator(it) {} + + /// post-increment (it++) + const_reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + const_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + const_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + const_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + const_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + const_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + const_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const const_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + const_reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } /// return the key of an object iterator typename object_t::key_type key() const diff --git a/test/unit.cpp b/test/unit.cpp index ccb79823..56ae526b 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -5492,6 +5492,369 @@ TEST_CASE("iterators") } } } + + SECTION("reverse iterator comparisons") + { + json j_values = {nullptr, true, 42, 23.23, {{"one", 1}, {"two", 2}}, {1, 2, 3, 4, 5}, "Hello, world"}; + + for (json& j : j_values) + { + auto it1 = j.rbegin(); + auto it2 = j.rbegin(); + auto it3 = j.rbegin(); + ++it2; + ++it3; + ++it3; + auto it1_c = j.crbegin(); + auto it2_c = j.crbegin(); + auto it3_c = j.crbegin(); + ++it2_c; + ++it3_c; + ++it3_c; + + // comparison: equal + { + CHECK(it1 == it1); + CHECK(not (it1 == it2)); + CHECK(not (it1 == it3)); + CHECK(not (it2 == it3)); + CHECK(it1_c == it1_c); + CHECK(not (it1_c == it2_c)); + CHECK(not (it1_c == it3_c)); + CHECK(not (it2_c == it3_c)); + } + + // comparison: not equal + { + // check definition + CHECK( (it1 != it1) == not(it1 == it1) ); + CHECK( (it1 != it2) == not(it1 == it2) ); + CHECK( (it1 != it3) == not(it1 == it3) ); + CHECK( (it2 != it3) == not(it2 == it3) ); + CHECK( (it1_c != it1_c) == not(it1_c == it1_c) ); + CHECK( (it1_c != it2_c) == not(it1_c == it2_c) ); + CHECK( (it1_c != it3_c) == not(it1_c == it3_c) ); + CHECK( (it2_c != it3_c) == not(it2_c == it3_c) ); + } + + // comparison: smaller + { + if (j.type() == json::value_t::object) + { + CHECK_THROWS_AS(it1 < it1, std::domain_error); + CHECK_THROWS_AS(it1 < it2, std::domain_error); + CHECK_THROWS_AS(it2 < it3, std::domain_error); + CHECK_THROWS_AS(it1 < it3, std::domain_error); + CHECK_THROWS_AS(it1_c < it1_c, std::domain_error); + CHECK_THROWS_AS(it1_c < it2_c, std::domain_error); + CHECK_THROWS_AS(it2_c < it3_c, std::domain_error); + CHECK_THROWS_AS(it1_c < it3_c, std::domain_error); + } + else + { + CHECK(not (it1 < it1)); + CHECK(it1 < it2); + CHECK(it1 < it3); + CHECK(it2 < it3); + CHECK(not (it1_c < it1_c)); + CHECK(it1_c < it2_c); + CHECK(it1_c < it3_c); + CHECK(it2_c < it3_c); + } + } + + // comparison: less than or equal + { + if (j.type() == json::value_t::object) + { + CHECK_THROWS_AS(it1 <= it1, std::domain_error); + CHECK_THROWS_AS(it1 <= it2, std::domain_error); + CHECK_THROWS_AS(it2 <= it3, std::domain_error); + CHECK_THROWS_AS(it1 <= it3, std::domain_error); + CHECK_THROWS_AS(it1_c <= it1_c, std::domain_error); + CHECK_THROWS_AS(it1_c <= it2_c, std::domain_error); + CHECK_THROWS_AS(it2_c <= it3_c, std::domain_error); + CHECK_THROWS_AS(it1_c <= it3_c, std::domain_error); + } + else + { + // check definition + CHECK( (it1 <= it1) == not(it1 < it1) ); + CHECK( (it1 <= it2) == not(it2 < it1) ); + CHECK( (it1 <= it3) == not(it3 < it1) ); + CHECK( (it2 <= it3) == not(it3 < it2) ); + CHECK( (it1_c <= it1_c) == not(it1_c < it1_c) ); + CHECK( (it1_c <= it2_c) == not(it2_c < it1_c) ); + CHECK( (it1_c <= it3_c) == not(it3_c < it1_c) ); + CHECK( (it2_c <= it3_c) == not(it3_c < it2_c) ); + } + } + + // comparison: greater than + { + if (j.type() == json::value_t::object) + { + CHECK_THROWS_AS(it1 > it1, std::domain_error); + CHECK_THROWS_AS(it1 > it2, std::domain_error); + CHECK_THROWS_AS(it2 > it3, std::domain_error); + CHECK_THROWS_AS(it1 > it3, std::domain_error); + CHECK_THROWS_AS(it1_c > it1_c, std::domain_error); + CHECK_THROWS_AS(it1_c > it2_c, std::domain_error); + CHECK_THROWS_AS(it2_c > it3_c, std::domain_error); + CHECK_THROWS_AS(it1_c > it3_c, std::domain_error); + } + else + { + // check definition + CHECK( (it1 > it1) == (it1 < it1) ); + CHECK( (it1 > it2) == (it2 < it1) ); + CHECK( (it1 > it3) == (it3 < it1) ); + CHECK( (it2 > it3) == (it3 < it2) ); + CHECK( (it1_c > it1_c) == (it1_c < it1_c) ); + CHECK( (it1_c > it2_c) == (it2_c < it1_c) ); + CHECK( (it1_c > it3_c) == (it3_c < it1_c) ); + CHECK( (it2_c > it3_c) == (it3_c < it2_c) ); + } + } + + // comparison: greater than or equal + { + if (j.type() == json::value_t::object) + { + CHECK_THROWS_AS(it1 >= it1, std::domain_error); + CHECK_THROWS_AS(it1 >= it2, std::domain_error); + CHECK_THROWS_AS(it2 >= it3, std::domain_error); + CHECK_THROWS_AS(it1 >= it3, std::domain_error); + CHECK_THROWS_AS(it1_c >= it1_c, std::domain_error); + CHECK_THROWS_AS(it1_c >= it2_c, std::domain_error); + CHECK_THROWS_AS(it2_c >= it3_c, std::domain_error); + CHECK_THROWS_AS(it1_c >= it3_c, std::domain_error); + } + else + { + // check definition + CHECK( (it1 >= it1) == not(it1 < it1) ); + CHECK( (it1 >= it2) == not(it1 < it2) ); + CHECK( (it1 >= it3) == not(it1 < it3) ); + CHECK( (it2 >= it3) == not(it2 < it3) ); + CHECK( (it1_c >= it1_c) == not(it1_c < it1_c) ); + CHECK( (it1_c >= it2_c) == not(it1_c < it2_c) ); + CHECK( (it1_c >= it3_c) == not(it1_c < it3_c) ); + CHECK( (it2_c >= it3_c) == not(it2_c < it3_c) ); + } + } + } + + // check exceptions if different objects are compared + for (auto j : j_values) + { + for (auto k : j_values) + { + if (j != k) + { + CHECK_THROWS_AS(j.rbegin() == k.rbegin(), std::domain_error); + CHECK_THROWS_AS(j.crbegin() == k.crbegin(), std::domain_error); + + CHECK_THROWS_AS(j.rbegin() < k.rbegin(), std::domain_error); + CHECK_THROWS_AS(j.crbegin() < k.crbegin(), std::domain_error); + } + } + } + } + + SECTION("reverse iterator arithmetic") + { + json j_object = {{"one", 1}, {"two", 2}, {"three", 3}}; + json j_array = {1, 2, 3, 4, 5, 6}; + json j_null = nullptr; + json j_value = 42; + + SECTION("addition and subtraction") + { + SECTION("object") + { + { + auto it = j_object.rbegin(); + CHECK_THROWS_AS(it += 1, std::domain_error); + } + { + auto it = j_object.crbegin(); + CHECK_THROWS_AS(it += 1, std::domain_error); + } + { + auto it = j_object.rbegin(); + CHECK_THROWS_AS(it + 1, std::domain_error); + } + { + auto it = j_object.crbegin(); + CHECK_THROWS_AS(it + 1, std::domain_error); + } + { + auto it = j_object.rbegin(); + CHECK_THROWS_AS(it -= 1, std::domain_error); + } + { + auto it = j_object.crbegin(); + CHECK_THROWS_AS(it -= 1, std::domain_error); + } + { + auto it = j_object.rbegin(); + CHECK_THROWS_AS(it - 1, std::domain_error); + } + { + auto it = j_object.crbegin(); + CHECK_THROWS_AS(it - 1, std::domain_error); + } + { + auto it = j_object.rbegin(); + CHECK_THROWS_AS(it - it, std::domain_error); + } + { + auto it = j_object.crbegin(); + CHECK_THROWS_AS(it - it, std::domain_error); + } + } + + SECTION("array") + { + { + auto it = j_array.rbegin(); + it += 3; + CHECK((j_array.rbegin() + 3) == it); + CHECK((it - 3) == j_array.rbegin()); + CHECK((j_array.rbegin() - it) == 3); + CHECK(*it == json(3)); + it -= 2; + CHECK(*it == json(5)); + } + { + auto it = j_array.crbegin(); + it += 3; + CHECK((j_array.crbegin() + 3) == it); + CHECK((it - 3) == j_array.crbegin()); + CHECK((j_array.crbegin() - it) == 3); + CHECK(*it == json(3)); + it -= 2; + CHECK(*it == json(5)); + } + } + + SECTION("null") + { + { + auto it = j_null.rbegin(); + it += 3; + CHECK((j_null.rbegin() + 3) == it); + CHECK((it - 3) == j_null.rbegin()); + CHECK((j_null.rbegin() - it) == 3); + CHECK(it != j_null.rend()); + it -= 3; + CHECK(it == j_null.rend()); + } + { + auto it = j_null.crbegin(); + it += 3; + CHECK((j_null.crbegin() + 3) == it); + CHECK((it - 3) == j_null.crbegin()); + CHECK((j_null.crbegin() - it) == 3); + CHECK(it != j_null.crend()); + it -= 3; + CHECK(it == j_null.crend()); + } + } + + SECTION("value") + { + { + auto it = j_value.rbegin(); + it += 3; + CHECK((j_value.rbegin() + 3) == it); + CHECK((it - 3) == j_value.rbegin()); + CHECK((j_value.rbegin() - it) == 3); + CHECK(it != j_value.rend()); + it -= 3; + CHECK(*it == json(42)); + } + { + auto it = j_value.crbegin(); + it += 3; + CHECK((j_value.crbegin() + 3) == it); + CHECK((it - 3) == j_value.crbegin()); + CHECK((j_value.crbegin() - it) == 3); + CHECK(it != j_value.crend()); + it -= 3; + CHECK(*it == json(42)); + } + } + } + + SECTION("subscript operator") + { + SECTION("object") + { + { + auto it = j_object.rbegin(); + CHECK_THROWS_AS(it[0], std::domain_error); + CHECK_THROWS_AS(it[1], std::domain_error); + } + { + auto it = j_object.crbegin(); + CHECK_THROWS_AS(it[0], std::domain_error); + CHECK_THROWS_AS(it[1], std::domain_error); + } + } + + SECTION("array") + { + { + auto it = j_array.rbegin(); + CHECK(it[0] == json(6)); + CHECK(it[1] == json(5)); + CHECK(it[2] == json(4)); + CHECK(it[3] == json(3)); + CHECK(it[4] == json(2)); + CHECK(it[5] == json(1)); + } + { + auto it = j_array.crbegin(); + CHECK(it[0] == json(6)); + CHECK(it[1] == json(5)); + CHECK(it[2] == json(4)); + CHECK(it[3] == json(3)); + CHECK(it[4] == json(2)); + CHECK(it[5] == json(1)); + } + } + + SECTION("null") + { + { + auto it = j_null.rbegin(); + CHECK_THROWS_AS(it[0], std::out_of_range); + CHECK_THROWS_AS(it[1], std::out_of_range); + } + { + auto it = j_null.crbegin(); + CHECK_THROWS_AS(it[0], std::out_of_range); + CHECK_THROWS_AS(it[1], std::out_of_range); + } + } + + SECTION("value") + { + { + auto it = j_value.rbegin(); + CHECK(it[0] == json(42)); + CHECK_THROWS_AS(it[1], std::out_of_range); + } + { + auto it = j_value.crbegin(); + CHECK(it[0] == json(42)); + CHECK_THROWS_AS(it[1], std::out_of_range); + } + } + } + } } TEST_CASE("capacity") @@ -9288,9 +9651,13 @@ TEST_CASE("regression tests") CHECK(b == json({0, 1, 2})); } { - // json a = {1,2,3}; - // json b = {0,0,0}; - // std::transform(++a.rbegin(),a.rend(),b.rbegin(),[](json el){return el;}); + json a = {1, 2, 3}; + json b = {0, 0, 0}; + std::transform(++a.rbegin(), a.rend(), b.rbegin(), [](json el) + { + return el; + }); + CHECK(b == json({0, 1, 2})); } }