4093. ranges::fold_left_first_with_iter incorrectly constructs optional<U>

Section: 26.6.18 [alg.fold] Status: New Submitter: Hewill Kang Opened: 2024-05-03 Last modified: 2024-06-24

Priority: 3

View other active issues in [alg.fold].

View all other issues in [alg.fold].

View all issues with New status.

Discussion:

ranges::fold_left_first_with_iter constructs the initial value through optional<U>(in_place, *first) which is not quite right, because the exposure constraint only ensures that U can be constructed from the value type of the iterator rather than its reference (demo):

#include <ranges>
#include <algorithm>

struct Op {
  Op() = default;
  Op(std::tuple<int>);
  Op(std::tuple<int&>) = delete;
  Op operator()(Op, std::tuple<int&>) const;
};

int main() {
  std::ranges::fold_left_first_with_iter(
    std::views::zip(std::views::single(0)), 
    Op{}
  ); // hard error in libstdc++ and MSVC-STL
}

[2024-06-24; Reflector poll]

Set priority to 3 after reflector poll. Unclear what "exposure constraint" means. "Don't want to require an extra copy to accommodate this corner case."

Proposed resolution:

This wording is relative to N4981.

  1. Modify 26.6.18 [alg.fold] as indicated:

    template<input_iterator I, sentinel_for<I> S
             indirectly-binary-left-foldable<iter_value_t<I>, I> F>
      requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
      constexpr see below ranges::fold_left_first_with_iter(I first, S last, F f);
    template<input_range R, indirectly-binary-left-foldable<range_value_t<R>, iterator_t<R>> F>
      requires constructible_from<range_value_t<R>, range_reference_t<R>>
      constexpr see below ranges::fold_left_first_with_iter(R&& r, F f);
    

    -9- Let U be

    decltype(ranges::fold_left(std::move(first), last, iter_value_t<I>(*first), f))
    

    -10- Effects: Equivalent to:

    if (first == last)
      return {std::move(first), optional<U>()};
    optional<U> init(in_place, iter_value_t<I>(*first));
    for (++first; first != last; ++first)
      *init = invoke(f, std::move(*init), *first);
    return {std::move(first), std::move(init)};
    

    -11- Remarks: The return type is fold_left_first_with_iter_result<I, optional<U>> for the first overload and fold_left_first_with_iter_result<borrowed_iterator_t<R>, optional<U>> for the second overload.