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;
}

Reply via email to