join_with_view::iterator
's iter_move
and iter_swap
should be conditionally noexcept
Section: 25.7.15.3 [range.join.with.iterator] Status: New Submitter: Hewill Kang Opened: 2023-01-06 Last modified: 2024-01-29
Priority: 3
View other active issues in [range.join.with.iterator].
View all other issues in [range.join.with.iterator].
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 25.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()
isfalse
.-?- 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()
andy.inner_it_.valueless_by_exception()
are eachfalse
.-?- 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&>()))