4103. ranges::unique_copy's constraints for the case where result is an input_iterator are not quite right

Section: 26.7.9 [alg.unique] Status: New Submitter: Hewill Kang Opened: 2024-05-14 Last modified: 2024-06-24

Priority: 3

View all other issues in [alg.unique].

View all issues with New status.

Discussion:

When r is only an input_range and result is also an input_iterator, ranges::unique_copy writes the elements of r into result and reads the value of result in the next iteration.

However, in this case, the function only requires that the value_type of r and the value_type of result are the same, which seems too loose because the value_type is not particularly useful in the ranges world compared to the reference, which is also reflected in the fact that the implementation applies the compare function on both dereferenced values (demo):

#include <algorithm>
#include <iostream>
#include <ranges>
#include <vector>

int main() {
  auto r = std::views::istream<bool>(std::cin);
  std::vector<bool> v(10);
  auto proj = [](std::same_as<bool> auto b) { return b; }; // ban vector<bool>::reference
  std::ranges::unique_copy(r, v.begin(), {}, proj);        // hard error in libstdc++, libc++ and MSVC-STL
}

[2024-06-24; Reflector poll]

Set priority to 3 after reflector poll.

Proposed resolution:

This wording is relative to N4981.

  1. Modify 26.4 [algorithm.syn], header <algorithm> synopsis, as indicated:

    #include <initializer_list>     // see [initializer.list.syn]
    
    namespace std {
      […]
      namespace ranges {
        template<class I, class O>
          using unique_copy_result = in_out_result<I, O>;
    
        template<input_iterator I, sentinel_for<I> S, weakly_incrementable O, class Proj = identity,
                 indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
          requires indirectly_copyable<I, O> &&
                   (forward_iterator<I> ||
                    (input_iterator<O> && same_as<iter_value_t<I>, iter_value_t<O>>
                     indirect_equivalence_relation<C, projected<I, Proj>, projected<O, Proj>>) ||
                    indirectly_copyable_storable<I, O>)
          constexpr unique_copy_result<I, O>
            unique_copy(I first, S last, O result, C comp = {}, Proj proj = {});
        template<input_range R, weakly_incrementable O, class Proj = identity,
                 indirect_equivalence_relation<projected<iterator_t<R>, Proj>> C = ranges::equal_to>
          requires indirectly_copyable<iterator_t<R>, O> &&
                   (forward_iterator<iterator_t<R>> ||
                    (input_iterator<O> && same_as<range_value_t<R>, iter_value_t<O>>
                     indirect_equivalence_relation<C, projected<iterator_t<R>, Proj>,
                                                      projected<O, Proj>>) ||
                    indirectly_copyable_storable<iterator_t<R>, O>)
          constexpr unique_copy_result<borrowed_iterator_t<R>, O>
            unique_copy(R&& r, O result, C comp = {}, Proj proj = {});
      }
      […]
    }
    
  2. Modify 26.7.9 [alg.unique] as indicated:

    template<input_iterator I, sentinel_for<I> S, weakly_incrementable O, class Proj = identity,
             indirect_equivalence_relation<projected<I, Proj>> C = ranges::equal_to>
      requires indirectly_copyable<I, O> &&
               (forward_iterator<I> ||
                (input_iterator<O> && same_as<iter_value_t<I>, iter_value_t<O>>
                 indirect_equivalence_relation<C, projected<I, Proj>, projected<O, Proj>>) ||
                indirectly_copyable_storable<I, O>)
      constexpr ranges::unique_copy_result<I, O>
        ranges::unique_copy(I first, S last, O result, C comp = {}, Proj proj = {});
    template<input_range R, weakly_incrementable O, class Proj = identity,
             indirect_equivalence_relation<projected<iterator_t<R>, Proj>> C = ranges::equal_to>
      requires indirectly_copyable<iterator_t<R>, O> &&
               (forward_iterator<iterator_t<R>> ||
               (input_iterator<O> && same_as<range_value_t<R>, iter_value_t<O>>
                indirect_equivalence_relation<C, projected<iterator_t<R>, Proj>,
                                                 projected<O, Proj>>) ||
                indirectly_copyable_storable<iterator_t<R>, O>)
      constexpr ranges::unique_copy_result<borrowed_iterator_t<R>, O>
        ranges::unique_copy(R&& r, O result, C comp = {}, Proj proj = {});
    

    -6- Let pred be equal_to{} for the overloads in namespace std with no parameter pred, […]