diff --git a/iterator.hpp b/iterator.hpp index 23a346ca..b5028833 100644 --- a/iterator.hpp +++ b/iterator.hpp @@ -22,6 +22,7 @@ #include "variadic.hpp" #include "view.hpp" +#include template class referencing_iterator { @@ -283,115 +284,116 @@ namespace util { /////////////////////////////////////////////////////////////////////////// namespace detail::zip { + template < + typename IteratorT, + typename = std::make_index_sequence> + > + struct iterator; + + + template + struct iterator> { + public: + // we cannot be a forward iterator because we don't want to supply + // references to a value type as that would necessitate storing + // said value types within the iterator. + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + + iterator (IteratorT _iterators): + m_iterators (_iterators) + { ; } + + + iterator& + operator++ (void) + { + std::tuple (++std::get (m_iterators)...); + return *this; + } + + + iterator operator++ (int); + + auto + operator* (void) + { + return std::forward_as_tuple (*std::get (m_iterators)...); + } + + + bool + operator== (const iterator &rhs) const + { + return m_iterators == rhs.m_iterators; + } + + + bool + operator!= (const iterator &rhs) const + { + return !(*this == rhs); + } + + private: + IteratorT m_iterators; + }; + + // holds a tuple of iterators for begin and end, and returns an // iterator that transforms these iterators into tuples of value_types. // // this must be expressed in terms of iterators, rather than containers, // because it dramatically simplifies iterating over raw arrays. // - // IteratorT: a tuple of iterators across all containers + // we have to store begin and end iterators because we might not have + // enough information to determine the correct types from StoreT; + // eg, in the case of arrays that have decayed to pointers we can't + // find the end. // - // StoreT: a tuple of containers we own. used when we were provided - // with an rval at zip time. allows us to destroy the data - // when we're actually done iterating. + // BeginT: a tuple of begin iterators across all containers + // + // EndT: a tuple of end iterators across all containers + // + // StoreT: a tuple of containers we might own. used when we were + // provided with an rval at zip time. allows us to destroy the + // data when we're actually done iterating. template < - typename IteratorT, typename StoreT, - typename I = std::make_index_sequence::value> + typename BeginT, + typename EndT, + typename I = std::make_index_sequence> > class collection; //--------------------------------------------------------------------- template < - typename IteratorT, typename StoreT, + typename BeginT, + typename EndT, std::size_t ...I > class collection< - IteratorT, StoreT, + BeginT, + EndT, std::index_sequence > { public: - collection (IteratorT _begin, IteratorT _end, StoreT &&_store): - m_begin { _begin }, - m_end { _end }, - m_store { std::forward (_store) } + collection (StoreT _store, BeginT _begin, EndT _end): + m_store { std::move (_store) }, + m_begin (std::move (_begin)), + m_end (std::move (_end)) { ; } - struct iterator : std::iterator< - std::forward_iterator_tag, - std::tuple< - typename std::iterator_traits< - typename std::tuple_element::type - >::value_type... - >, - std::size_t - > { - public: - iterator (IteratorT _iterators): - m_iterators (_iterators) - { ; } - - - iterator& - operator++ (void) - { - // HACK: we don't actually need to create a tuple here, - // but it's a zero cost method to expand the parameter - // pack. - std::make_tuple (++std::get (m_iterators)...); - return *this; - } - - - iterator operator++ (int); - - - auto - operator* (void) - { - return std::make_tuple (*std::get (m_iterators)...); - } - - - bool - operator== (const iterator &rhs) const - { - return m_iterators == rhs.m_iterators; - } - - - bool - operator!= (const iterator &rhs) const - { - return !(*this == rhs); - } - - private: - IteratorT m_iterators; - }; - - - iterator - begin (void) - { - return iterator { { std::get (m_begin)... } }; - } - - - iterator - end (void) - { - return iterator { { std::get (m_end)... } }; - } - + auto begin (void)& { return iterator { m_begin }; } + auto end (void)& { return iterator { m_end }; } private: - IteratorT m_begin; - IteratorT m_end; - const StoreT m_store; + StoreT m_store; + BeginT m_begin; + EndT m_end; }; } @@ -408,19 +410,16 @@ namespace util { auto zip (ContainerT&&... data) { - using IteratorT = std::tuple; - - auto store = util::variadic::filter (std::forward (data)...); - return detail::zip::collection< - IteratorT, - decltype(store), + decltype (std::forward_as_tuple (data...)), + decltype (std::make_tuple (std::begin (data)...)), + decltype (std::make_tuple (std::end (data)...)), std::make_index_sequence - > { + > ( + std::forward_as_tuple (data...), std::make_tuple (std::begin (data)...), - std::make_tuple (std::end (data)...), - std::move (store) - }; + std::make_tuple (std::end (data)...) + ); }; diff --git a/test/iterator.cpp b/test/iterator.cpp index 7dda5d39..9cf04eab 100644 --- a/test/iterator.cpp +++ b/test/iterator.cpp @@ -12,18 +12,42 @@ main (int, char**) { util::TAP::logger tap; - std::vector v_int { 1, 2, 3 }; - std::array a_float { 1.1f, 2.2f, 3.3f }; - char c_char[] = { 'a', 'b', 'c' }; + // test that iteration across disparate types works as expected. + // + // the char array is an important element because it tends to decay to a + // pointer type unless we're paying careful attention. + { + std::vector v_int { 1, 2, 3 }; + std::array a_float { 1.1f, 2.2f, 3.3f }; + char c_char[] = { '\0', 'b', 'c' }; - bool success = true; - for (const auto &[i, v, a, c]: util::izip (v_int, a_float, c_char)) { - success = success && - v_int[i] == v && - util::equal (a_float[i], a) && - c_char[i] == c; + bool success = true; + for (auto [i, v, a, c]: util::izip (v_int, a_float, c_char)) { + success = success && + v_int[i] == v && + util::equal (a_float[i], a) && + c_char[i] == c; + } + + tap.expect (success, "izip containers of int, float, and char and an initialiser_list"); + } + + + // test that the obvious structured binding syntax gives references to + // the underlying values rather than silently supplying lvalues. + // + // we deliberately do not use any type of references in the range loop so + // that we check the syntax a user is likely to employ actually works. + // references may not be an obvious consideration. + { + const std::array src { 0, 1, 2 }; + std::array dst { 2, 0, 1 }; + + for (auto [a,b]: util::zip (src, dst)) + b = a; + + tap.expect_eq (src, dst, "copy using structured bindings"); } - tap.expect (success, "izip containers of int, float, and char and an initialiser_list"); return tap.status (); } diff --git a/test/string.cpp b/test/string.cpp index ffc91186..7f882e81 100644 --- a/test/string.cpp +++ b/test/string.cpp @@ -30,8 +30,9 @@ main (int, char**) }; util::view src { csv.c_str (), csv.size () }; - for (const auto &[tok, expected]: util::zip (util::tokeniser (src, ','), TESTS)) + for (auto &&[tok, expected]: util::zip (util::tokeniser (src, ','), TESTS)) { tap.expect (equal (tok, expected.value), "%s", expected.message); + } return tap.status (); } \ No newline at end of file