Title
ranges::fold_* can unintentionally const_cast and reinterpret_cast
Status
nad
Section
[alg.fold]
Submitter
Nicole Mazzuca

Created on 2022-09-15.00:00:00 last changed 16 months ago

Messages

Date: 2022-11-30.17:59:24

Proposed resolution:

This wording is relative to N4917.

  1. Modify [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)};
    
Date: 2022-11-30.00:00:00

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

Date: 2022-10-15.00:00:00

[ 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."

Date: 2022-09-15.00:00:00

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.

History
Date User Action Args
2022-11-30 17:59:24adminsetmessages: + msg13141
2022-10-12 14:38:10adminsetmessages: + msg12852
2022-10-12 14:38:10adminsetstatus: new -> nad
2022-09-17 15:36:52adminsetmessages: + msg12771
2022-09-15 00:00:00admincreate