This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
Section: 26.7.15.3 [range.join.with.iterator] Status: New Submitter: Hewill Kang Opened: 2023-01-06 Last modified: 2023-02-01
Priority: 3
View all issues with New status.
Discussion:
In order to preserve room for optimization, the standard always tries to propagate the noexcept specification of custom iter_move/iter_swap for different iterators.
But for join_with_view::iterator, these two specializations are the only ones in the standard that do not have a noexcept specification. This is because both invoke visit in the function body, and visit may throw an exception when the variant does not hold a value.
However, implementors are not required to follow the standard practice. Since the join_with_view::iterator's variant member only contains two alternative types, both libstdc++ and MSVC-STL avoid heavyweight visit calls by simply using multiple if statements. This means that it is still possible to add a conditional noexcept specification to these overloads, and there is already a precedent in the standard, namely common_iterator. All we need to do is add a Preconditions.
[2023-02-01; Reflector poll]
Set priority to 3 after reflector poll. "The iter_swap specification is wrong since we can swap Pattern and Inner. And this is something implementations can strengthen."
Proposed resolution:
This wording is relative to N4917.
Modify 26.7.15.3 [range.join.with.iterator] as indicated:
[…]namespace std::ranges { template<input_range V, forward_range Pattern> requires view<V> && input_range<range_reference_t<V>> && view<Pattern> && compatible-joinable-ranges<range_reference_t<V>, Pattern> template<bool Const> class join_with_view<V, Pattern>::iterator { […] Parent* parent_ = nullptr; // exposition only OuterIter outer_it_ = OuterIter(); // exposition only variant<PatternIter, InnerIter> inner_it_; // exposition only […] public: […] friend constexpr decltype(auto) iter_move(const iterator& x) noexcept(see below);{ using rvalue_reference = common_reference_t< iter_rvalue_reference_t<InnerIter>, iter_rvalue_reference_t<PatternIter>>; return visit<rvalue_reference>(ranges::iter_move, x.inner_it_); }friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(see below) requires indirectly_swappable<InnerIter, PatternIter>;{ visit(ranges::iter_swap, x.inner_it_, y.inner_it_); }}; }friend constexpr decltype(auto) iter_move(const iterator& x) noexcept(see below);-?- Let rvalue_reference be:
common_reference_t<iter_rvalue_reference_t<InnerIter>, iter_rvalue_reference_t<PatternIter>>-?- Preconditions: x.inner_it_.valueless_by_exception() is false.
-?- Effects: Equivalent to: return visit<rvalue_reference>(ranges::iter_move, x.inner_it_);
-?- Remarks: The exception specification is equivalent to:
noexcept(ranges::iter_move(declval<const InnerIter&>())) && noexcept(ranges::iter_move(declval<const PatternIter&>())) && is_nothrow_convertible_v<iter_rvalue_reference_t<InnerIter>, rvalue_reference> && is_nothrow_convertible_v<iter_rvalue_reference_t<PatternIter>, rvalue_reference>friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(see below) requires indirectly_swappable<InnerIter, PatternIter>;-?- Preconditions: x.inner_it_.valueless_by_exception() and y.inner_it_.valueless_by_exception() are each false.
-?- Effects: Equivalent to: visit(ranges::iter_swap, x.inner_it_, y.inner_it_).
-?- Remarks: The exception specification is equivalent to:
noexcept(ranges::iter_swap(declval<const InnerIter&>(), declval<const InnerIter&>())) && noexcept(ranges::iter_swap(declval<const PatternIter&>(), declval<const PatternIter&>()))