3463. Incorrect requirements for transform_inclusive_scan without initial value

Section: 26.10.11 [transform.inclusive.scan] Status: New Submitter: Agustín K-ballo Bergé Opened: 2020-07-07 Last modified: 2020-09-06

Priority: 3

View all issues with New status.

Discussion:

The requirements for the overloads of std::transform_inclusive_scan without an initial value incorrectly assume that the internal accumulator uses the iterator's value type, as it does for std::inclusive_scan, rather than the transformed type of the iterator's value type, as it was intended.

According to the standard, the following program is ill-formed as it requires std::string to be convertible to int:

auto vs = {0, 1, 2};
std::transform_inclusive_scan(
   vs.begin(), vs.end(),
   std::ostream_iterator<std::string>(std::cout, ";"),
   [](std::string x, std::string y) { return x + y; },
   [](int x) { return std::to_string(x); });

libstdc++ and Microsoft's STL accept the snippet, producing 0;01;012; as expected, libc++ strictly conforms to the standard and rejects it.

These constrains were introduced by P0574R1.

[2020-07-17; Priority set to 3 in telecon]

Proposed resolution:

This wording is relative to N4861.

[Drafting note: Current implementations that accept the code, do some form of auto acc = unary_op(*first);, therefore the following proposed wording uses decay_t instead of e.g. remove_cvref_t.]

  1. Modify 26.10.11 [transform.inclusive.scan] as indicated:

    template<class InputIterator, class OutputIterator,
             class BinaryOperation, class UnaryOperation>
      constexpr OutputIterator
        transform_inclusive_scan(InputIterator first, InputIterator last,
                                 OutputIterator result,
                                 BinaryOperation binary_op, UnaryOperation unary_op);
    template<class ExecutionPolicy,
             class ForwardIterator1, class ForwardIterator2,
             class BinaryOperation, class UnaryOperation>
      ForwardIterator2
        transform_inclusive_scan(ExecutionPolicy&& exec,
                                 ForwardIterator1 first, ForwardIterator1 last,
                                 ForwardIterator2 result,
                                 BinaryOperation binary_op, UnaryOperation unary_op);
    template<class InputIterator, class OutputIterator,
             class BinaryOperation, class UnaryOperation, class T>
      constexpr OutputIterator
        transform_inclusive_scan(InputIterator first, InputIterator last,
                                 OutputIterator result,
                                 BinaryOperation binary_op, UnaryOperation unary_op,
                                 T init);
    template<class ExecutionPolicy,
             class ForwardIterator1, class ForwardIterator2,
             class BinaryOperation, class UnaryOperation, class T>
      ForwardIterator2
        transform_inclusive_scan(ExecutionPolicy&& exec,
                                 ForwardIterator1 first, ForwardIterator1 last,
                                 ForwardIterator2 result,
                                 BinaryOperation binary_op, UnaryOperation unary_op,
                                 T init);
    

    -1- Let U be the value type of decltype(first)decay_t<decltype(unary_op(*first))>.

    -2- […]