<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title><code>views::transform_join</code></title>
  <style type="text/css">
    body {
      font-variant-ligatures: none;
    }

    p {
      text-align: justify
    }

    li {
      text-align: justify
    }

    blockquote.note,
    div.note {
      background-color: #E0E0E0;
      padding-left: 15px;
      padding-right: 15px;
      padding-top: 1px;
      padding-bottom: 1px;
    }

    p code {
      color: navy
    }

    ins p code {
      color: #00A000
    }

    p ins code {
      color: #00A000
    }

    p del code {
      color: #A00000
    }

    ins {
      color: #00A000
    }

    del {
      color: #A00000
    }

    table#boilerplate {
      border: 0
    }

    table#boilerplate td {
      padding-left: 2em
    }

    table.bordered,
    table.bordered th,
    table.bordered td {
      border: 1px solid;
      text-align: center;
    }

    ins.block {
      color: #00A000;
      text-decoration: none
    }

    del.block {
      color: #A00000;
      text-decoration: none
    }

    #hidedel:checked~* del,
    #hidedel:checked~* del * {
      display: none;
      visibility: hidden
    }
  </style>
</head>

<body data-new-gr-c-s-check-loaded="14.1043.0" data-gr-ext-installed="">
  <table id="boilerplate">
    <tbody>
      <tr>
        <td>Document number</td>
        <td>R3211R0</td>
      </tr>
      <tr>
        <td>Date</td>
        <td>2024-03-31</td>
      </tr>
      <tr>
        <td>Audience</td>
        <td>LEWG, SG9 (Ranges)</td>
      </tr>
      <tr>
        <td>Reply-to</td>
        <td>Hewill Kang &lt;hewillk@gmail.com&gt;</td>
      </tr>
    </tbody>
  </table>
  <hr>
  <h1><code>views::transform_join</code></h1>
  <ul>
    <li>
      <ul>
        <li>Abstract</li>
        <li>Revision history</li>
        <li>Discussion</li>
        <li>Design</li>
        <li>Implementation experience</li>
        <li>Proposed change</li>
        <li>References</li>
      </ul>
    </li>
  </ul>
  <a name="Abstract"></a>
  <h2>Abstract</h2>
  <p>
    This paper proposes the Tier 1 adaptor in <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html">P2760</a>:
    <code>views::transform_join</code> to improve the C++26 ranges facilities,
    which is a simple composition of <code>views::transform</code> and <code>views::join</code>.
  </p>
  <a name="Revision history"></a>
  <h2>Revision history</h2>
  <p>
  <h3>R0</h3>
  <p>Initial revision.</p>
  </p>
  </ol>
  </p>
  <a name="Discussion"></a>
  <h2>Discussion</h2>
  <p>
    <code>views::transform_join</code>, called <code>flat_map</code> in most languages, is a commonly-used operation on
    data streams.
    It transforms the input elements into an output sequence through the transformation function, and flattens the
    resulting sequence of the sequence.
  </p>
  <p>
    Benefiting from the enhancement of <code>join_view</code> in <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2328r1.html">P2328</a>,
    <code>views::transform_join(E, F)</code> is now completely
    equivalent to <code>views::join(views::transform(E, F))</code>. We no longer need to use
    <code>views::cache_last</code> (<a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3138r0.html">P3138</a>) to cache
    the prvalue range returned by the transform function,
    <code>join_view</code> already do this internally for us.
  </p>
  <p>
    Given that <a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html">P2760</a> considers this
    adaptor a high priority, the commonness of such operation in other languages, and the convenience brought by
    reducing the user's
    typing, the author believes that it is time to bring <code>views::transform_join</code> into the standard.
  </p>
  <p>
    After this, the famous Pythagorean triples <a
      href="https://github.com/ericniebler/range-v3/blob/master/example/comprehensions.cpp#L22-L44">formula in
      range/v3</a> now can be written as:
  </p>
  <pre>
    // Define an infinite range containing all the Pythagorean triples:
    auto triples = views::iota(1) | views::transform_join([](int z) {
      return views::iota(1, z + 1) | views::transform_join([=](int x) {
        return views::iota(x, z + 1) | views::transform_join([=](int y) {
          return views::repeat(tuple(x, y, z), x * x + y * y == z * z ? 1 : 0);
        });
      });
    });

    // Display the first 100 triples
    for (auto triple : triples | views::take(100))
      println("{}", triple);
</pre>
  </ol>

  <a name="Design"></a>
  <h2>Design</h2>
  <p>
  <h3>Should we name it <code>views::flat_map</code>?</h3>
  <p>
    Nope.</p>
  <p></p>
  Although most other languages call this operation <code>flat_map</code>,
  given that the standard already has a container of the same name (i.e. <code>std::flat_map</code>) with a completely
  different meaning, the author believes that <code>views::transform_join</code> is a
  more intuitive and standard-compliant name as it is the composition of <code>views::transform</code> and
  <code>views::join</code>.
  </p>
  <p>It's worth noting that range/v3 calls it <code>views::for_each</code>, which might be a <a
      href="https://stackoverflow.com/a/53347369/11638718">more
      confusing name</a> so is not considered here.
  </p>
  <h3>Should we introduce a new <code>transform_join_view</code> class?</h3>
  <p>
  <p>
    Nope.</p>
  <p></p> As <a
    href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html#simple-adaptor-compositions">stated in
    P2760</a>: "Importantly, there really isn't much benefit to providing a bespoke <code>transform_join</code> as
  opposed to simply implementing it in terms of these two existing adaptors."
  The author holds the same view.</p>
  <p>
    Although this will bring an adaptor that combines <i>two</i> existing adaptors to the standard for the first time,
    the author believes that such change is still worthwhile.
  </p>
  <p>
    First of all, the cost of creating a new <code>transform_join_view</code> class and associated iterator is quite
    high because <code>join_view</code> is already a complex view.
    Especially when its functionality can be completely synthesized by simply combining two adaptors, introducing new
    classes has no observable benefit except bringing unnecessary complexity and redundancy.
  </p>
  <p>
    Second, by combining two adaptors, <code>views::transform_join</code> can automatically inherit their previous
    and subsequent enhancements, such as
    support for stashing iterators (<a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2770r0.html">P2770</a>) and
    extension for conditionally borrowed (<a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3117r0.html">P3117</a>, hope so?), because
    <code>views::transform_join</code> is essentially a specialized <code>join_view</code>.
  </p>
  <p>In summary, the authors stick with the current design. Note that the design is also consistent with the <a
      href="https://github.com/ericniebler/range-v3/blob/53c40dd628450c977ee1558285ff43e0613fa7a9/include/range/v3/view/for_each.hpp#L53">implementation
      in range/v3</a>.</p>
  <h3>Should we spell it
    <code>views::join(views::transform(E, F))</code> or <code>join_view(transform_view(E, F))</code>?
  </h3>
  <p>As currently worded, <code>views::transform(E, F)</code> is expression-equivalent to
    <code>transform_view(E, F)</code> and <code>views::join(E)</code> is expression-equivalent to
    <code>join_view&lt;views::all_t&lt;decltype((E))&gt;&gt;{E}</code>.
    Since the latter's <code>E</code> is always <code>transform_view</code>, this causes <code>views::all_t</code> to
    have no effect.
  </p>
  <p>That is to say, <code>views::join(views::transform(E, F))</code> is exactly
    <code>join_view(transform_view(E, F))</code>. In this case, the author prefers to use the more primitive form of the
    latter as the wording.
  </p>
  <a name="Implementation experience"></a>
  <h2>Implementation experience</h2>
  <p>The author implemented <code>views::transform_join</code> based on libc++.
    The details are relatively simple in less than 20 lines, see <a href="https://godbolt.org/z/1ve8jchP8">here</a>.</p>
  <a name="Proposed-change"></a>
  <h2>Proposed change</h2>
  <p>This wording is relative to <a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4971.pdf">N4971</a>.
  </p>
  </div>
  <div>
    <ol>
      <ol>
        <li>
          <p>Add a new feature-test macro to 17.3.2 <a href="https://eel.is/c++draft/version.syn">[version.syn]</a>:
          </p>
          <blockquote>
            <pre><ins>#define __cpp_lib_ranges_transform_join 2024XXL // <i>freestanding, also in</i> &lt;ranges&gt;</ins></pre>
          </blockquote>
        </li>
        <li>
          <p>Modify 26.2 <a href="https://eel.is/c++draft/ranges.syn">[ranges.syn]</a>, Header
            <code>&lt;ranges&gt;</code>
            synopsis, as indicated:
          </p>

          <blockquote>
            <pre>
#include &lt;compare&gt;              // <i>see <a href="https://eel.is/c++draft/compare.syn">[compare.syn]</a></i>
#include &lt;initializer_list&gt;     // <i>see <a href="https://eel.is/c++draft/initializer.list.syn">[initializer.list.syn]</a></i>
#include &lt;iterator&gt;             // <i>see <a href="https://eel.is/c++draft/iterator.synopsis">[iterator.synopsis]</a></i>

namespace std::ranges {
  [&hellip;]
  namespace views { inline constexpr <i>unspecified</i> join_with = <i>unspecified</i>; }         // <i>freestanding</i>

<ins>  // <i>[range.transform.join]</a>, transform join view</i>
  namespace views { inline constexpr <i>unspecified</i> transform_join = <i>unspecified</i>; }    // <i>freestanding</i></ins>
  [&hellip;]
}
        </pre>
          </blockquote>
        </li>
        <li>
          <p>Add <b>26.7.? Transform join view [range.transform.join]</b> after 26.7.15 <a
              href="https://eel.is/c++draft/range.join.with">[range.join.with]</a> as indicated:</p>
          <p>
            -1- A transform-join view flattens the result of an underlying sequence after applying a transformation
            function that returns another range to each element.
          </p>
          <p>-2- The name <code>views::transform_join</code> denotes a range adaptor object (<a
              href="https://eel.is/c++draft/range.adaptor.object">[range.adaptor.object]</a>). Given subexpressions
            <code>E</code> and <code>F</code>,
            the expression <code>views::transform_join(E, F)</code> is expression-equivalent to
            <code>join_view(transform_view(E, F))</code>.
          </p>
          <p>-3- [<i>Example 1</i>:</p>
          <pre>  vector ints{0, 1, 2};
  for (auto&& elem : ints | views::transform_join([](int i) { return views::repeat(i, 3); }))
    cout &lt;&lt; elem &lt;&lt; ' '; // <i>prints</i> 0 0 0 1 1 1 2 2 2 </pre>
          — <i>end example]</i>
        </li>
        </blockquote>
        </blockquote>
        </li>
      </ol>
    </ol>
  </div>
  <a name="References"></a>
  <h2>References</h2>
  <dd>
  <dt id="biblio-p2760">[P2760R1]
  <dd>Barry Revzin. A Plan for C++26 Ranges. URL: <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html">https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html</a>
  <dt id="biblio-p2328">[P2328R1]
  <dd>Tim Song. <code>join_view</code> should join all views of ranges. URL: <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2328r1.html">https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2328r1.html</a>
  <dt id="biblio-p3138">[P3138R0]
  <dd>Tim Song. <code>views::cache_last</code>. URL: <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3138r0.html">https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3138r0.html</a>
  <dt id="biblio-p2770">[P2770R0]
  <dd>Tim Song. Stashing stashing iterators for proper flattening. URL: <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2770r0.html">https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2770r0.html</a>
  <dt id="biblio-p3117">[P3117R0]
  <dd>Zach Laine. Extending Conditionally Borrowed. URL: <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3117r0.html">https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3117r0.html</a>
</body>

</html>