Title
`ranges::for_each(_n)` should be less constrained
Status
new
Section
[alg.foreach]
Submitter
Jiang An

Created on 2025-04-08.00:00:00 last changed 3 weeks ago

Messages

Date: 2025-04-13.12:44:39

Proposed resolution:

This wording is relative to N5008.

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

    […]
    namespace ranges {
      template<class I, class F>
      using for_each_result = in_fun_result<I, F>;
      
      template<input_iterator I, sentinel_for<I> S, class Proj = identity,
               indirectly_unary_invocable<projected<I, Proj>>move_constructible Fun>
        requires invocable<Fun&, iter_reference_t<projected<I, Proj>>>
        constexpr for_each_result<I, Fun>
          for_each(I first, S last, Fun f, Proj proj = {});
      template<input_range R, class Proj = identity,
               indirectly_unary_invocable<projected<iterator_t<R>, Proj>>move_constructible Fun>
        requires invocable<Fun&, iter_reference_t<projected<iterator_t<R>, Proj>>>
        constexpr for_each_result<borrowed_iterator_t<R>, Fun>
          for_each(R&& r, Fun f, Proj proj = {});
    }
    […]
    namespace ranges {
      template<class I, class F>
      using for_each_n_result = in_fun_result<I, F>;
    
      template<input_iterator I, class Proj = identity,
               indirectly_unary_invocable<projected<I, Proj>>move_constructible Fun>
         requires invocable<Fun&, iter_reference_t<projected<I, Proj>>>
        constexpr for_each_n_result<I, Fun>
          for_each_n(I first, iter_difference_t<I> n, Fun f, Proj proj = {});
    }
    […]
    
  2. Modify [alg.foreach] as indicated:

    template<input_iterator I, sentinel_for<I> S, class Proj = identity,
             indirectly_unary_invocable<projected<I, Proj>>move_constructible Fun>
      requires invocable<Fun&, iter_reference_t<projected<I, Proj>>>
      constexpr ranges::for_each_result<I, Fun>
        ranges::for_each(I first, S last, Fun f, Proj proj = {});
    template<input_range R, class Proj = identity,
             indirectly_unary_invocable<projected<iterator_t<R>, Proj>>move_constructible Fun>
      requires invocable<Fun&, iter_reference_t<projected<iterator_t<R>, Proj>>>
      constexpr ranges::for_each_result<borrowed_iterator_t<R>, Fun>
        ranges::for_each(R&& r, Fun f, Proj proj = {});
    

    […]

    -15- [Note 6: The overloads in namespace `ranges` require `Fun` to model `copy_constructible`. — end note]

    […]

    template<input_iterator I, class Proj = identity,
             indirectly_unary_invocable<projected<I, Proj>>move_constructible Fun>
      requires invocable<Fun&, iter_reference_t<projected<I, Proj>>>
      constexpr ranges::for_each_n_result<I, Fun>
        ranges::for_each_n(I first, iter_difference_t<I> n, Fun f, Proj proj = {});
    

    […]

    -30- [Note 11: The overload in namespace `ranges` requires `Fun` to model `copy_constructible`. — end note]

Date: 2025-04-08.00:00:00

Currently, `ranges::for_each(_n)` are constrained with `indirectly_unary_invocable`, which doesn't meet the actual use of the range elements. These algorithms are only expected to invoke the callable object with the possibly projected elements, and not to use any element as the value type. Moreover, `indirectly_unary_invocable` requires the callable object to be copy constructible, which might be undesired because the corresponding `std::for_each(_n)` only require move constructibilty.

LWG 4171 talked about the breakage around `ranges::for_each` introduced by P2609R3. P2609R3 looks like a reasonable fix as long as the affected algorithms potentially use the intermediate element values copied as std::iter_value_t<I>. However, when the algorithm is not expected to or even required not to do this, P2609R3 can bring unexpected impacts. It seems that constraints around `iter_value_t` should be avoided for such an algorithm.

History
Date User Action Args
2025-04-13 08:52:56adminsetmessages: + msg14719
2025-04-08 00:00:00admincreate