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.

3852. join_with_view::iterator's iter_move and iter_swap should be conditionally noexcept

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.

  1. 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&>()))