https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110905
--- Comment #4 from danakj at orodu dot net --- Ok it only happens if the VecIntoIter class has a base class, even when it's empty the bug reproduces. But when I remove the IteratorBase base class, then GCC compiles it correctly. It's getting hard to remove anything and keep it to repro at this point. Replacing Option with std::optional also made it stop, for whatever reason. https://godbolt.org/z/a9PcsKTMf ``` #include <concepts> #include <cstddef> #include <memory> #include <utility> template <class T> class Vec; constexpr int from_sum(auto&& it) noexcept { auto p = int(0); while (true) { auto i = it.next(); if (!i.has_value()) break; p += *i; } return p; } template <class T> struct Storage final { constexpr ~Storage() requires(std::is_trivially_destructible_v<T>) = default; constexpr ~Storage() requires(!std::is_trivially_destructible_v<T>) {} constexpr Storage(const Storage&) requires(std::is_trivially_copy_constructible_v<T>) = default; constexpr Storage& operator=(const Storage&) requires(std::is_trivially_copy_assignable_v<T>) = default; constexpr Storage(Storage&&) requires(std::is_trivially_move_constructible_v<T>) = default; constexpr Storage& operator=(Storage&&) requires(std::is_trivially_move_assignable_v<T>) = default; constexpr Storage() {} constexpr Storage(const std::remove_cvref_t<T>& t) : val_(t), state_(true) {} constexpr Storage(std::remove_cvref_t<T>& t) : val_(t), state_(true) {} constexpr Storage(std::remove_cvref_t<T>&& t) : val_(std::move(t)), state_(true) {} __attribute__((pure)) constexpr const T& val() const { return val_; } __attribute__((pure)) constexpr T& val_mut() { return val_; } __attribute__((pure)) constexpr inline bool state() const noexcept { return state_; } constexpr inline void construct_from_none(const T& t) noexcept requires(std::is_copy_constructible_v<T>) { std::construct_at(&val_, t); state_ = true; } constexpr inline void construct_from_none(T&& t) noexcept { std::construct_at(&val_, std::move(t)); state_ = true; } constexpr inline void set_some(const T& t) noexcept requires(std::is_copy_constructible_v<T>) { if (state_ == false) construct_from_none(t); else val_ = t; state_ = true; } constexpr inline void set_some(T&& t) noexcept { if (state_ == false) construct_from_none(std::move(t)); else val_ = std::move(t); state_ = true; } [[nodiscard]] constexpr inline T replace_some(T&& t) noexcept { return std::exchange(val_, std::move(t)); } [[nodiscard]] constexpr inline T take_and_set_none() noexcept { state_ = false; auto taken = T(static_cast<T&&>(val_)); val_.~T(); return taken; } constexpr inline void set_none() noexcept { state_ = false; val_.~T(); } constexpr inline void destroy() noexcept { val_.~T(); } private: union { T val_; }; bool state_ = false; }; template <class T> class Option final { static_assert(!std::is_reference_v<T>); static_assert(!std::is_const_v<T>); public: inline constexpr Option() noexcept = default; static inline constexpr Option with(const T& t) noexcept requires(std::is_copy_constructible_v<T>) { return Option(t); } static inline constexpr Option with(T&& t) noexcept { if constexpr (std::is_move_constructible_v<T>) { return Option(std::move(t)); } else { return Option(t); } } constexpr ~Option() noexcept requires(std::is_trivially_destructible_v<T>) = default; constexpr inline ~Option() noexcept requires(!std::is_trivially_destructible_v<T>) { if (t_.state()) t_.destroy(); } constexpr Option(Option&& o) requires(std::is_trivially_move_constructible_v<T>) = default; constexpr Option(Option&& o) noexcept requires(!std::is_trivially_move_constructible_v<T>) { if (o.t_.state()) t_.construct_from_none(o.t_.take_and_set_none()); } constexpr Option(Option&& o) requires(!std::is_move_constructible_v<T>) = delete; constexpr Option& operator=(Option&& o) requires(std::is_trivially_move_assignable_v<T>) = default; constexpr Option& operator=(Option&& o) noexcept requires(!std::is_trivially_move_assignable_v<T>) { if (o.t_.state()) t_.set_some(o.t_.take_and_set_none()); else if (t_.state()) t_.set_none(); return *this; } constexpr Option& operator=(Option&& o) requires(!std::is_move_constructible_v<T>) = delete; __attribute__((pure)) constexpr bool has_value() const noexcept { return t_.state(); } __attribute__((pure)) constexpr const std::remove_reference_t<T>& operator*() const& noexcept { return t_.val(); } __attribute__((pure)) constexpr std::remove_reference_t<T>& operator*() & noexcept { return t_.val_mut(); } private: template <class U> friend class Option; constexpr explicit Option(const T& t) : t_(t) {} constexpr explicit Option(T&& t) : t_(std::move(t)) {} Storage<T> t_; }; template <class Iter, class Item> class IteratorBase { public: }; template <class EachIter, class InnerSizedIter> class [[nodiscard]] Flatten : public IteratorBase<Flatten<EachIter, InnerSizedIter>, typename EachIter::Item> { public: using Item = typename EachIter::Item; constexpr Flatten(InnerSizedIter&& iters) : iters_(std::move(iters)) {} constexpr Option<Item> next() noexcept { Option<Item> out; while (true) { if (front_iter_.has_value()) { out = (*front_iter_).next(); if (out.has_value()) return out; front_iter_ = Option<EachIter>(); } front_iter_ = [](auto&& i) { if (!i.has_value()) return Option<EachIter>(); return Option<EachIter>::with(std::move(*i).into_iter()); }(iters_.next()); if (!front_iter_.has_value()) break; } return out; } constexpr Item sum() && noexcept { return from_sum(std::move(*this)); } private: InnerSizedIter iters_; Option<EachIter> front_iter_; }; template <class ItemT> struct [[nodiscard]] VecIntoIter : public IteratorBase<VecIntoIter<ItemT>, ItemT> { public: using Item = ItemT; static constexpr auto with(Vec<Item>&& vec) noexcept { return VecIntoIter(std::move(vec)); } constexpr Option<Item> next() noexcept { if (front_index_ == back_index_) [[unlikely]] return Option<Item>(); Item& item = vec_[std::exchange(front_index_, front_index_ + size_t{1})]; return Option<Item>::with(std::move(item)); } constexpr auto flatten() && noexcept { using Flatten = Flatten< decltype(std::declval<std::remove_cvref_t<Item>&&>().into_iter()), VecIntoIter>; return Flatten(std::move(*this)); } private: constexpr VecIntoIter(Vec<Item>&& vec) noexcept : vec_(std::move(vec)) {} Vec<Item> vec_; size_t front_index_ = size_t{0}; size_t back_index_ = vec_.len(); }; template <class T> class Vec final { static_assert(!std::is_reference_v<T>); static_assert(!std::is_const_v<T>); public: template <std::convertible_to<T>... Ts> static inline constexpr Vec with(Ts&&... values) noexcept { auto v = Vec(nullptr, size_t{0}, size_t{0}); v.grow_to_exact(sizeof...(Ts)); (..., v.push(std::forward<Ts>(values))); return v; } constexpr ~Vec() { if (is_alloced()) free_storage(); } constexpr Vec(Vec&& o) noexcept : data_(std::exchange(o.data_, nullptr)), len_(std::exchange(o.len_, 0)), capacity_(std::exchange(o.capacity_, 0)) {} constexpr Vec& operator=(Vec&& o) noexcept { if (is_alloced()) free_storage(); data_ = std::exchange(o.data_, nullptr); len_ = std::exchange(o.len_, 0); capacity_ = std::exchange(o.capacity_, 0); return *this; } constexpr void grow_to_exact(size_t cap) noexcept { if (cap <= capacity_) return; const auto bytes = sizeof(T) * cap; if (!is_alloced()) { data_ = std::allocator<T>().allocate(size_t{cap}); capacity_ = cap; return; } T* new_allocation = std::allocator<T>().allocate(size_t{cap}); T* old_t = data_; T* new_t = new_allocation; const size_t self_len = len(); for (size_t i = 0; i < self_len; i += 1u) { std::construct_at(new_t, std::move(*old_t)); old_t->~T(); ++old_t; ++new_t; } std::allocator<T>().deallocate(data_, size_t{capacity_}); data_ = new_allocation; capacity_ = cap; } constexpr void reserve(size_t additional) noexcept { if (len() + additional <= capacity_) return; grow_to_exact(apply_growth_function(additional)); } constexpr void push(T t) noexcept requires(std::is_move_constructible_v<T>) { reserve(size_t{1}); const auto self_len = len(); std::construct_at(data_ + self_len, std::move(t)); len_ = self_len + size_t{1}; } constexpr VecIntoIter<T> into_iter() && noexcept requires(std::is_move_constructible_v<T>) { return VecIntoIter<T>::with(std::move(*this)); } __attribute__((pure)) constexpr T& operator[](size_t i) & noexcept { return *(data_ + i); } __attribute__((pure)) constexpr inline size_t len() const& noexcept { return len_; } private: constexpr Vec(T* ptr, size_t len, size_t cap) : data_(ptr), len_(len), capacity_(cap) {} constexpr size_t apply_growth_function(size_t additional) const noexcept { size_t goal = additional + len(); size_t cap = capacity_; while (cap < goal) { cap = (cap + 1u) * 3u; auto bytes = sizeof(T) * cap; } return cap; } constexpr void free_storage() { if constexpr (!std::is_trivially_destructible_v<T>) { const auto self_len = len(); for (size_t i = 0; i < self_len; i += 1u) (data_ + i)->~T(); } std::allocator<T>().deallocate(data_, size_t{capacity_}); } constexpr inline bool is_alloced() const noexcept { return capacity_ > size_t{0}; } T* data_; size_t len_; size_t capacity_; }; int main() { static_assert(Vec<Vec<int>>::with(Vec<int>::with(1, 2, 3), // Vec<int>::with(4), // Vec<int>::with(), // Vec<int>::with(5, 6)) .into_iter() .flatten() .sum() == 1 + 2 + 3 + 4 + 5 + 6); } ```