https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91780
Bug ID: 91780 Summary: Discrepancy between gcc 7.4, through 9.2, compared to clang. Product: gcc Version: 9.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: mikael.p.persson at gmail dot com Target Milestone: --- GCC does not change values in tuple of references The bug is indicated by a difference between gcc7.4 to 9.2 and gcc trunk and clang 6.0 to 8.0 clang succeeds, gcc fails. I think clang is right, but I am not absolutely sure. Compiler from godbolt Flags: -std=c++17 Problem occurs regardless of optimization levels. -fsanitize-undefined shows no problem I am uncertain where the problem occurs, and the smallest case I have is as follows: #include <tuple> #include <iterator> #include <vector> #include <list> #include <functional> #include <assert.h> template <typename Fn, typename Argument, std::size_t... Ns> auto tuple_map_impl(Fn&& fn, Argument&& argument, std::index_sequence<Ns...>) { if constexpr (sizeof...(Ns) == 0) return std::tuple<>(); // empty tuple else if constexpr (std::is_same_v<decltype(fn(std::get<0>(argument))), void>) { [[maybe_unused]] auto _ = {(fn(std::get<Ns>(argument)), 0)...}; // no return value expected return; } // then dispatch lvalue, rvalue ref, temporary else if constexpr (std::is_lvalue_reference_v<decltype(fn(std::get<0>(argument)))>) { return std::tie(fn(std::get<Ns>(argument))...); } else if constexpr (std::is_rvalue_reference_v<decltype(fn(std::get<0>(argument)))>) { return std::forward_as_tuple(fn(std::get<Ns>(argument))...); } else { return std::tuple(fn(std::get<Ns>(argument))...); } } template <typename T> constexpr bool is_tuple_impl_v = false; template <typename... Ts> constexpr bool is_tuple_impl_v<std::tuple<Ts...>> = true; template <typename T> constexpr bool is_tuple_v = is_tuple_impl_v<std::decay_t<T>>; template <typename Fn, typename Tuple> auto tuple_map(Fn&& fn, Tuple&& tuple) { static_assert(is_tuple_v<Tuple>, "tuple_map implemented only for tuples"); return tuple_map_impl(std::forward<Fn>(fn), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>()); } template <typename... Iterators> class zip_iterator { public: using value_type = std::tuple<typename std::decay_t<Iterators>::value_type...>; using difference_type = std::size_t; using pointer = value_type*; using reference = value_type&; using iterator_category = std::forward_iterator_tag; public: zip_iterator(Iterators... iterators) : iters(iterators...) {} zip_iterator(const std::tuple<Iterators...>& iter_tuple) : iters(iter_tuple) {} zip_iterator(const zip_iterator&) = default; zip_iterator(zip_iterator&&) = default; zip_iterator& operator=(const zip_iterator&) = default; zip_iterator& operator=(zip_iterator&&) = default; bool operator != (const zip_iterator& other) const { return iters != other.iters; } zip_iterator& operator++() { tuple_map([](auto& iter) { ++iter; }, iters); return *this; } zip_iterator operator++(int) { auto tmp = *this; ++(*this); return tmp; } auto operator*() { return tuple_map([](auto i) -> decltype(auto) { return *i; }, iters); } auto operator*() const { return tuple_map([](auto i) -> decltype(auto) { return *i; }, iters); } private: std::tuple<Iterators...> iters; }; template <typename... Containers> struct zip { using iterator = zip_iterator<decltype(std::remove_reference_t<Containers>().begin())...>; template <typename... Container_types> zip(Containers... containers) : containers_(std::forward<Containers>(containers)...) { } auto begin() { return iterator(tuple_map([](auto&& i) { return std::begin(i); }, containers_)); } auto end() { return iterator(tuple_map([](auto&& i) { return std::end(i); }, containers_)); } std::tuple<Containers...> containers_; }; template <typename... Container_types> zip(Container_types&&... containers) -> zip<Container_types...>; int main(){ std::vector<int> as{1,2,3}; std::vector<int> bs{-1,-2,-3}; // tuple of references references are returned, // so they should be changed for (auto [x, y] : zip(as, bs)) { x++;y--; } // check that the result is changed, // this succeeds on clang 6.0.0, 8.0.0, fails on gcc 7.4.0, 9.2.0 assert(as[0]== 2); assert(as[1]== 3); assert(as[2]== 4); assert(bs[0]==-2); assert(bs[1]==-3); assert(bs[2]==-4); return 0; }