3779. ranges::fold_* can unintentionally const_cast and reinterpret_cast

Section: 26.6.18 [alg.fold] Status: NAD Submitter: Nicole Mazzuca Opened: 2022-09-15 Last modified: 2022-11-30

Priority: Not Prioritized

View other active issues in [alg.fold].

View all other issues in [alg.fold].

View all issues with NAD status.

Discussion:

In the Effects element of ranges::fold_right, we get the following code:

using U = decay_t<invoke_result_t<F&, iter_reference_t<I>, T>>;
if (first == last)
  return U(std::move(init)); // functional-style C cast
[…]

Given the following function object:

struct Second {
  static char* operator()(const char*, char* rhs) {
    return rhs;
  }
};

calling fold_right as:

char* p = fold_right(views::empty<char*>, "Hello", Second{});

initializes p with const_cast<char*>("Hello").

The same problem exists in fold_left_with_iter, and thus in fold_left.

One can get the reinterpret_cast behavior by replacing const char* with unsigned long long.

[2022-10-12; Reflector poll]

Set status to "Tentatively NAD" after reflector poll.

"The example doesn't compile. The accumulator should be be the second param, but with that fixed the constraints are not satisfied. The convertible_to constraint prevents the undesirable casting."

[2022-11-30 LWG telecon. Status changed: Tentatively NAD → NAD.]

Proposed resolution:

This wording is relative to N4917.

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

    template<bidirectional_iterator I, sentinel_for<I> S, class T,
             indirectly-binary-right-foldable<T, I> F>
      constexpr auto ranges::fold_right(I first, S last, T init, F f);
    template<bidirectional_range R, class T,
             indirectly-binary-right-foldable<T, iterator_t<R>> F>
      constexpr auto ranges::fold_right(R&& r, T init, F f);
    

    -3- Effects: Equivalent to:

    using U = decay_t<invoke_result_t<F&, iter_reference_t<I>, T>>;
    if (first == last)
      return static_cast<U>(std::move(init));
    I tail = ranges::next(first, last);
    U accum = invoke(f, *--tail, std::move(init));
    while (first != tail)
      accum = invoke(f, *--tail, std::move(accum));
    return accum;
    
    […]
    template<input_iterator I, sentinel_for<I> S, class T,
             indirectly-binary-left-foldable<T, I> F>
      constexpr see below ranges::fold_left_with_iter(I first, S last, T init, F f);
    template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F>
      constexpr see below ranges::fold_left_with_iter(R&& r, T init, F f);
    

    -6- Let U be decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>.

    -7- Effects: Equivalent to:

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