<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title><code>views::flat_map</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>P3211R1</td>
      </tr>
      <tr>
        <td>Date</td>
        <td>2024-07-04</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::flat_map</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>
    We propose <code>views::flat_map</code>, a range adaptor that applies a function returning a range for each element,
    then flattens the result.
    This pattern, commonly known as <i>flat mapping</i>, is widespread in functional programming and data processing.
    Providing it as a dedicated view improves readability and expressiveness, and also opens opportunities for
    optimization in lazy evaluation contexts.
  </p>
  <p>Noted that this is ranked as Tier 1 in <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html">P2760</a>.</p>
  <a name="Revision history"></a>
  <h2>Revision history</h2>
  <p>
  <h3>R0</h3>
  <p>Initial revision.</p>
  <h3>R1</h3>
  <p>Rename <code>views::transform_join</code> to <code>views::flat_map</code>.</p>
  <p>Introduce new <code>flat_map_view</code> class.</p>
  <p>Discuss related optimization.</p>
  </p>
  </ol>
  </p>
  <a name="Discussion"></a>
  <h2>Discussion</h2>
  <p>
    Mapping each element to a subrange and flattening the results into a single range is common in programming tasks
    like data processing, string handling, and range composition.</p>
  <p>
    This appears frequently enough in practice to justify direct support in the standard ranges library.
    Providing <code>views::flat_map</code> encourages clearer, more maintainable code and lays the groundwork for
    potential <i>optimizations</i> specific to this use case. While the same behavior can be achieved through
    composition such as <code>views::transform</code> and then <code>views::join</code>, a dedicated view enables the
    library to make stronger semantic guarantees and allows more efficient
    handling of transform function results, particularly for expensive or lvalue-producing mappers.</p>
  <p>
    This pattern arises naturally in code like:
  </p>
  <pre>
  auto all_courses = students 
                   | std::views::flat_map([](const Student& s) {
                       return std::views::all(s.courses);
                     });
</pre>
  <a name="Design"></a>
  <h2>Design</h2>

  <p>
  <h3>Why <code>views::flat_map</code> instead of <code>views::transform_join</code>?</h3>

  <p>
    The name <code>transform_join</code> describes how to combine <code>transform</code> and <code>join</code>, but it
    does not show the real concept. Users may see it as two steps, not as a single <i>map and flatten</i> operation;
    The name <code>flat_map</code> is common in many languages and clearly shows both mapping and flattening. This makes
    it easier to find, less confusing, and matches what users expect.
  </p>
  <p>
    The following table shows how this operation is named in various languages, all of which converge on
    <i>flat-map</i>:
  </p>

  <style>
    table {
      border-collapse: collapse;
      font-family: sans-serif;
    }

    th,
    td {
      border: 1px solid #aaa;
      padding: 8px 12px;
      text-align: left;
    }

    th {
      background-color: #f0f0f0;
    }
  </style>
  <table border="1" cellpadding="6" cellspacing="0">
    <thead>
      <tr>
        <th>Language</th>
        <th>Function Name</th>
        <th>Example</th>
        <th>Differences in Semantics</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Haskell</td>
        <td><code>concatMap</code><br><code>(>>=)</code></td>
        <td><code>concatMap f [1, 2, 3]</code></td>
        <td>Purely functional; <code>>>=</code> is monadic bind, generalizing flat mapping beyond lists</td>
      </tr>
      <tr>
        <td>C# (LINQ)</td>
        <td><code>SelectMany</code></td>
        <td><code>source.SelectMany(x =&gt; ...)</code></td>
        <td>Applies to all IEnumerable; default for flattening nested queries in LINQ syntax</td>
      </tr>
      <tr>
        <td>Python</td>
        <td><code>itertools.chain.from_iterable(map(...))</code></td>
        <td><code>list(chain.from_iterable(map(f, data)))</code></td>
        <td>No built-in <code>flatMap</code>; semantics are manual; relies on strict evaluation and eager lists</td>
      </tr>
      <tr>
        <td>Java</td>
        <td><code><span style="color:red;font-weight:bolder">flatMap</span></code></td>
        <td><code>stream.flatMap(f)</code></td>
        <td>Requires the mapper function to return a Stream; lazy evaluation is enforced</td>
      </tr>
      <tr>
        <td>JavaScript</td>
        <td><code><span style="color:red;font-weight:bolder">flatMap</span></code></td>
        <td><code>array.flatMap(x =&gt; [x, x + 1])</code></td>
        <td>One-level flattening only; not lazy; limited to arrays</td>
      </tr>
      <tr>
        <td>Kotlin</td>
        <td><code><span style="color:red;font-weight:bolder">flatMap</span></code></td>
        <td><code>list.flatMap { listOf(it, it * 2) }</code></td>
        <td>Similar to Java, but cleaner syntax; strict (not lazy) evaluation for standard collections</td>
      </tr>
      <tr>
        <td>Rust</td>
        <td><code><span style="color:red;font-weight:bolder">flat_map</span></code></td>
        <td><code>iter.flat_map(|x| some_iter(x))</code></td>
        <td>Applies to iterators; lazy by default; consumes the original iterator</td>
      </tr>
      <tr>
        <td>Swift</td>
        <td><code><span style="color:red;font-weight:bolder">flatMap</span></code></td>
        <td><code>array.flatMap { [$0, $0 * 2] }</code></td>
        <td>Semantics changed in Swift 4 - used to also remove optionals; now strictly flatten + map</td>
      </tr>
    </tbody>
  </table>
  <p>
    Given this widespread usage, <code>flat_map</code> is the most appropriate and intuitive name for the proposed view.
    It reflects established terminology, avoids over-specifying implementation details, and aligns well with programmer
    expectations.
  </p>

  <p>
    It is worth noting that the <code>range/v3</code> uses the name <code>views::for_each</code> for this operation.
    Because
    such naming deviates further from common terminology and could cause additional confusion, it is not considered a
    suitable option.
  </p>

  <p>
    Additionally, C++23 introduced a container named <code>std::flat_map</code>, which is a associative
    container that stores key-value pairs.
    This is conceptually quite different from the proposed <code>views::flat_map</code>, which is a range adaptor that
    composes a map-then-flatten operation. Clarifying this distinction helps avoid confusion between the two.
  </p>

  <p>
    Finally, while <code>flat_map</code> is preferred for its clarity and alignment with established usage, other naming
    alternatives are acceptable as long as they convey the correct semantics.
  </p>
  <h3>Why shouldn't be <code>views::transform(f) | views::join</code>?</h3>
  <p>
    Although <code>flat_map</code> can be expressed as a composition of <code>transform</code> and
    <code>join</code>, a dedicated <code>flat_map_view</code> provides clearer semantics and more efficient
    behavior in practice.
  </p>

  <p>
    The first minor issue with composition is that it interferes with <code>base()</code>. When writing
    <code>transform(f) | join</code>, the result holds a <code>transform_view</code> internally, so calling
    <code>base()</code> does not give access to the original range, which can be surprising and inconvenient in generic
    contexts.
  </p>

  <p>
    Second, a standalone view can manage both the outer iterator and the inner range. It can cache the
    transform result and
    avoid <i>repeated</i> calls, which is important when the function returns an lvalue range.</p>
  <p>In the composed form,
    <code>join_view</code> has no knowledge
    of how the
    inner range was produced - it treats each element as if it were obtained by dereferencing the outer iterator. This
    works fine when the base is something like vector of vectors, where dereferencing is
    cheap
    and deterministic. But with <code>transform | join</code>, each inner range is the result of invoking a user-defined
    transformation, which could be expensive to compute even if the result is a stable lvalue. For example:
  </p>
  <pre>
  auto outer   = views::iota(1, 5);
  auto inner   = views::iota(1, 10);
  auto flatten = views::flat_map(outer, [&](int i) -&gt; <span style="color:red;font-weight:bolder">auto&</span> { return inner; });
  println("{}", flatten);      // <span style="color:red;font-weight:bolder">calls transform function 40 times</span></pre>
  <p>
    While <code>cache_latest</code> can mitigate redundant evaluation, it forces the result to model only an
    <code>input_range</code>, regardless of the actual capabilities of the underlying ranges. It also adds an extra
    layer of adaptor composition and complexity. In contrast, a dedicated <code>flat_map_view</code> can handle this
    caching internally while preserving the strongest valid iterator category.
  </p>

  <p>
    A dedicated view also avoids unnecessary template instantiations. Since a flat-mapped range can never be more than
    bidirectional, there is no benefit in preserving random-access capabilities through <code>transform_view</code>.
    However, when using adaptor composition, those capabilities may still be instantiated - even though
    <code>join_view</code> will downgrade them - leading to code bloat and slower compilation.
  </p>

  <p>
    In short, using composition is flexible but less efficient and can be awkward. <code>flat_map_view</code> avoids
    repeated evaluations, keeps the best iterator type, and makes the view stack simpler.
  </p>

  <p>
    Although paper <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html#simple-adaptor-compositions">
      P2760</a> suggests there is little benefit to providing a
    separat view as state: "<i>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.</i>", practical usage has revealed that
    the composed form introduces subtle inefficiencies, especially in the presence of expensive transformation
    functions, and not easy to eliminate without internal knowledge of how adaptors interact.
  </p>
  <h3>How to store inner ranges</h3>
  <p>When the inner range is a reference, we can avoid repeated invocations of the mapping function by storing a pointer
    to the result instead. This allows us to simply dereference the pointer whenever the inner range is needed, rather
    than invoking the transformation again — a technique somewhat analogous to <code>cache_latest_view</code>. However, it's important to
    note that this pointer must be stored inside the iterator, not the <code>flat_map_view</code> itself, in order to ensure correct
    behavior in multi-pass scenarios where multiple iterators may coexist and advance independently.</p>
  <p>For prvalue inner ranges, the situation is somewhat more tricky, especially for <i>immovable</i> ranges.</p>
  <p>For <code>join_view</code>, it underlying range's iterator already returns the inner
    range.
    This property makes it directly suitable for <code><i>non-propagating-cache</i>::<i>emplace-deref</i></code>, which
    caches the inner range by
    dereferencing the outer iterator.

  <p>In contrast, <code>flat_map_view</code> obtains the inner range by applying a mapping function to the element
    referenced by the
    outer iterator, rather than by directly dereferencing the outer iterator to yield the inner range. As a result, it
    cannot straightforwardly use <code><i>emplace-deref</i></code> to cache the inner range. Instead, it
    must store the
    entire inner range object returned by the mapping function within it to properly extend its lifetime.</p>

  <p>One possible approach to mitigate this is to define a proxy iterator that models invoking the mapping function upon
    dereference. This proxy can then be used with <code><i>emplace-deref</i></code> to cache the mapped inner range
    indirectly, albeit with
    additional complexity.</p>
  <h3>Support for stashing flattening</h3>
  <p>At first glance, <code>flat_map_view</code> might not have a stashing issue as discussed in <a
      href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2770r0.html">
      P2770</a>, since its inner range is always produced by applying a mapping function, rather than being a subrange
    stored within the outer range. </p>
  <p>However, in particular, the mapping function can certainly return a range whose lifetime is tied to the
    outer
    iterator -
    for example, applying <code>views::split('x')</code> to strings obtained from an
    <code>istream_iterator&lt;string&gt;</code> — then
    <code>flat_map_view</code> need cache the current outer iterator to ensures the inner range remains
    valid
    during iteration. Therefore, <code>flat_map_view</code> follows the design pattern of that paper.
  </p>
  <a name="Implementation experience"></a>
  <h2>Implementation experience</h2>
  <p>The author implemented <code>views::flat_map</code> based on libstdc++, see <a
      href="https://godbolt.org/z/zrzd9ox8h">here</a>.</p>
  <p>The implementation supports <code>input_range</code>,
    <code>forward_range</code>, and
    <code>bidirectional_range</code>, and demonstrates correct caching behavior for both lvalue and prvalue immovable
    mapped
    ranges.
  </p>
  <a name="Proposed-change"></a>
  <h2>Proposed change</h2>
  <p>This wording is relative to <a href="https://eel.is/c++draft">Latest Working Draft
    </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_flat_map 2025XXL // <i>freestanding, also in</i> &lt;ranges&gt;</ins></pre>
          </blockquote>
        </li>
        <li>
          <p>Modify 25.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>
// <i>mostly freestanding</i>
#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>; }

<ins>  // <i>[range.flat.map]</a>, flat map view</i>
  template&lt;input_range V, move_constructible F&gt; 
    requires <i>see below</i>
  class flat_map_view;

  namespace views { inline constexpr <i>unspecified</i> flat_map = <i>unspecified</i>; }</ins>
  [&hellip;]
}
        </pre>
          </blockquote>
        </li>
        <li>
          <p>Add <b>25.7.? Flat map view [range.flat.map]</b> after 25.7.15 <a
              href="https://eel.is/c++draft/range.join.with">[range.join.with]</a> as indicated:</p>
          <p>
            -1-
            A flat-map view maps each element to a range and flattens the results into a view.
          </p>
          <p>-2- The name <code>views::flat_map</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::flat_map(E, F)</code> is expression-equivalent to
            <code>flat_map_view(E, F)</code>.
          </p>
          <p>-3- [<i>Example 1</i>:</p>
          <pre>  vector ints{0, 1, 2};
  for (auto elem : ints | views::flat_map([](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>
          <p>[25.7.?.2] Class template <code>flat_map_view</code> [range.flat.map]</p>
          <pre>
namespace std::ranges {
  template&lt;class R, class F&gt; 
  concept <i>flat-mappable</i> = input_range&lt;R&gt; &&                        // <i>exposition only</i>
                          regular_invocable&lt;F&, range_reference_t&lt;R&gt;&gt; &&
                          input_range&lt;invoke_result_t&lt;F&, range_reference_t&lt;R&gt;&gt;&gt;;

  template&lt;input_range V, move_constructible F&gt; 
    requires view&lt;V&gt; && is_object_v&lt;F&gt; && <i>flat-mappable</i>&lt;V, F&gt;
  class flat_map_view : public view_interface&lt;flat_map_view&lt;V, F&gt;&gt; {
  private:
    using <i>MappedRng</i> = invoke_result_t&lt;F&, range_reference_t&lt;V&gt;&gt;;  // <i>exposition only</i>

    // <i>[range.flat.map.iterator], class template</i> flat_map_view::<i>iterator</i>
    template&lt;bool Const&gt;
      struct <i>iterator</i>;                                            // <i>exposition only</i>

    // <i>[range.flat.map.sentinel], class template</i> flat_map_view::<i>sentinel</i>
    template&lt;bool Const&gt;
      struct <i>sentinel</i>;                                            // <i>exposition only</i>

    V <i>base_</i> = V();                                                // <i>exposition only</i>
    <i>movable-box</i>&lt;F&gt; <i>fun_</i>;                                          // <i>exposition only</i>

    <i>non-propagating-cache</i>&lt;iterator_t&lt;V&gt;&gt; <i>current_</i>;                // <i>exposition only, present only</i>
                                                                  // <i>when</i> !forward_range&lt;V&gt;
    <i>non-propagating-cache</i>&lt;<i>MappedRng</i>&gt; <i>mapped_</i>;                     // <i>exposition only, present only</i>
                                                                  // <i>if</i> is_reference_v&lt;<i>MappedRng</i>&gt; <i>is</i> false
  public:
    flat_map_view() requires default_initializable&lt;V&gt; && default_initializable&lt;F&gt; = default;
    constexpr explicit flat_map_view(V base, F fun);

    constexpr V base() const & requires copy_constructible&lt;V&gt; { return <i>base_</i>; }
    constexpr V base() && { return std::move(<i>base_</i>); }

    constexpr auto begin() {
      if constexpr (forward_range&lt;V&gt;) {
        constexpr bool use_const = <i>simple-view</i>&lt;V&gt; && is_reference_v&lt;<i>MappedRng</i>&gt;;
        return <i>iterator</i>&lt;use_const&gt;{*this, ranges::begin(<i>base_</i>)};
      } else {
        <i>current_</i> = ranges::begin(<i>base_</i>);
        return <i>iterator</i>&lt;false&gt;{*this};
      }
    }

    constexpr auto begin() const
      requires forward_range&lt;const V&gt; &&
               <i>flat-mappable</i>&lt;const V, const F&gt; &&
               is_reference_v&lt;invoke_result_t&lt;const F&, range_reference_t&lt;const V&gt;&gt;&gt;
    { return <i>iterator</i>&lt;true&gt;{*this, ranges::begin(<i>base_</i>)}; }

    constexpr auto end() {
      if constexpr (forward_range&lt;V&gt; &&
                    is_reference_v&lt;<i>MappedRng</i>&gt; && forward_range&lt;<i>MappedRng</i>&gt; &&
                    common_range&lt;V&gt; && common_range&lt;<i>MappedRng</i>&gt;)
        return <i>iterator</i>&lt;<i>simple-view</i>&lt;V&gt;&gt;{*this, ranges::end(<i>base_</i>)};
      else
        return <i>sentinel</i>&lt;<i>simple-view</i>&lt;V&gt;&gt;{*this};
    }

    constexpr auto end() const
      requires forward_range&lt;const V&gt; &&
               <i>flat-mappable</i>&lt;const V, const F&gt; &&
               is_reference_v&lt;invoke_result_t&lt;const F&, range_reference_t&lt;const V&gt;&gt;&gt; {
      using MappedConstRng = invoke_result_t&lt;const F&, range_reference_t&lt;const V&gt;&gt;;
      if constexpr (forward_range&lt;MappedConstRng&gt; &&
                    common_range&lt;const V&gt; && common_range&lt;MappedConstRng&gt;)
        return <i>iterator</i>&lt;true&gt;{*this, ranges::end(<i>base_</i>)};
      else
        return <i>sentinel</i>&lt;true&gt;{*this};
    }
  };

  template&lt;class R, class F&gt;
    flat_map_view(R&amp;&amp;, F) -&gt; flat_map_view&lt;views::all_t&lt;R&gt;, F&gt;;
}</pre>
          <pre>constexpr explicit flat_map_view(V base, F fun);</pre>
          <blockquote>
            <p>-1- <i>Effects</i>: Initializes <code><i>base_</i></code> with <code>std::move(base)</code>,
              and <code><i>fun_</i></code> with <code>std::move(fun)</code>.
            </p>
          </blockquote>
          <p>[25.7.?.3] Class template <code>flat_map_view::<i>iterator</i></code> [range.flat.map.iterator]</p>
          <pre>
namespace std::ranges {
  template&lt;input_range V, move_constructible F&gt; 
    requires view&lt;V&gt; && is_object_v&lt;F&gt; && <i>flat-mappable</i>&lt;V, F&gt;
  template&lt;bool Const&gt;
  struct flat_map_view&lt;V, F&gt;::<i>iterator</i> {
  private:
    using <i>Parent</i>     = <i>maybe-const</i>&lt;Const, flat_map_view&gt;;         // <i>exposition only</i>
    using <i>Base</i>       = <i>maybe-const</i>&lt;Const, V&gt;;                     // <i>exposition only</i>
    using <i>MappedBase</i> =                                            // <i>exposition only</i>
      invoke_result_t&lt;<i>maybe-const</i>&lt;Const, F&gt;&, range_reference_t&lt;<i>Base</i>&gt;&gt;;
    using <i>BaseIter</i>   = iterator_t&lt;<i>Base</i>&gt;;                          // <i>exposition only</i>
    using <i>MappedIter</i> = iterator_t&lt;<i>MappedBase</i>&gt;;                    // <i>exposition only</i>

    static constexpr bool <i>mapped-is-glvalue</i> =                     // <i>exposition only</i>
      is_reference_v&lt;<i>MappedBase</i>&gt;;

    <i>BaseIter</i> <i>current_</i> = <i>BaseIter</i>();                               // <i>exposition only, present only</i>
                                                                  // <i>if Base models</i> forward_range
    optional&lt;<i>MappedIter</i>&gt; <i>mapped_it_</i>;                              // <i>exposition only</i>
    <i>Parent</i>* <i>parent_</i> = nullptr;                                    // <i>exposition only</i>
    add_pointer_t&lt;<i>MappedBase</i>&gt; <i>mapped_</i> = nullptr;                  // <i>exposition only, present only</i>
                                                                  // <i>if mapped-is-glvalue is</i> true

    constexpr <i>BaseIter</i>& <i>current</i>();                                // <i>exposition only</i>
    constexpr const <i>BaseIter</i>& <i>current</i>() const;                    // <i>exposition only</i>
    constexpr auto& <i>update-mapped</i>();                              // <i>exposition only</i>
    constexpr auto& <i>get-mapped</i>();                                 // <i>exposition only</i>
    constexpr void <i>satisfy</i>();                                     // <i>exposition only</i>

    constexpr <i>iterator</i>(<i>Parent</i>& parent, <i>BaseIter</i> current)
      requires forward_range&lt;<i>Base</i>&gt;;                               // <i>exposition only</i>
    constexpr explicit <i>iterator</i>(<i>Parent</i>& parent)
      requires (!forward_range&lt;<i>Base</i>&gt;);                            // <i>exposition only</i>

  public:
    using iterator_concept  = <i>see below</i>;
    using iterator_category = <i>see below</i>;                          // <i>not always present</i>
    using value_type        = range_value_t&lt;<i>MappedBase</i>&gt;;
    using difference_type   = <i>see below</i>;

    <i>iterator</i>() = default;

    template&lt;class MappedRng = invoke_result_t&lt;F&, range_reference_t&lt;V&gt;&gt;&gt;
    constexpr <i>iterator</i>(iterator&lt;!Const&gt; i)
      requires Const &&
               is_reference_v&lt;MappedRng&gt; &&
               convertible_to&lt;iterator_t&lt;V&gt;, <i>BaseIter</i>&gt; &&
               convertible_to&lt;iterator_t&lt;MappedRng&gt;, <i>MappedIter</i>&gt; &&
               convertible_to&lt;add_pointer_t&lt;MappedRng&gt;, add_pointer_t&lt;<i>MappedBase</i>&gt;&gt;;

    constexpr decltype(auto) operator*() const { return **<i>mapped_it_</i>; }

    constexpr <i>MappedIter</i> operator-&gt;() const
      requires <i>has-arrow</i>&lt;<i>MappedIter</i>&gt; && copyable&lt;<i>MappedIter</i>&gt;;

    constexpr <i>iterator</i>& operator++();
    constexpr void operator++(int);
    constexpr <i>iterator</i> operator++(int)
      requires <i>mapped-is-glvalue</i> && forward_range&lt;<i>Base</i>&gt; &&
               forward_range&lt;<i>MappedBase</i>&gt;;

    constexpr <i>iterator</i>& operator--()
      requires <i>mapped-is-glvalue</i> && bidirectional_range&lt;<i>Base</i>&gt; &&
               <i>bidirectional-common</i>&lt;<i>MappedBase</i>&gt;;

    constexpr <i>iterator</i> operator--(int)
      requires <i>mapped-is-glvalue</i> && bidirectional_range&lt;<i>Base</i>&gt; &&
               <i>bidirectional-common</i>&lt;<i>MappedBase</i>&gt;;

    friend constexpr bool operator==(const <i>iterator</i>& x, const <i>iterator</i>& y)
      requires <i>mapped-is-glvalue</i> && forward_range&lt;<i>Base</i>&gt; &&
               equality_comparable&lt;iterator_t&lt;<i>MappedBase</i>&gt;&gt;;

    friend constexpr decltype(auto) iter_move(const <i>iterator</i>& i)
      noexcept(noexcept(ranges::iter_move(*i.<i>mapped_it_</i>))) {
      return ranges::iter_move(*i.<i>mapped_it_</i>);
    }

    friend constexpr void iter_swap(const <i>iterator</i>& x, const <i>iterator</i>& y)
      noexcept(noexcept(ranges::iter_swap(*x.<i>mapped_it_</i>, *y.<i>mapped_it_</i>)))
      requires indirectly_swappable&lt;<i>MappedIter</i>&gt;;
  };
}</pre>
          <blockquote>
            <p>-1- <code>iterator::iterator_concept</code> is defined as follows:</p>
            <ol style="list-style-type: none">
              <li>
                <p>(1.1) &mdash; If <code><i>mapped-is-glvalue</i></code> is true, <code><i>Base</i></code> models
                  <code>bidirectional_range</code>, and
                  <code><i>MappedBase</i></code> models <code><i>bidirectional-common</i></code>, then
                  <code>iterator_concept</code>
                  denotes <code>bidirectional_iterator_tag</code>.
                </p>
              </li>
              <li>
                <p>(1.2) &mdash; Otherwise, if <code><i>mapped-is-glvalue</i></code> is <code>true</code> and
                  <code><i>Base</i></code> and <code><i>MappedBase</i></code> each
                  model <code>forward_range</code>, then <code>iterator_concept</code> denotes
                  <code>forward_iterator_tag</code>.
                </p>
              </li>
              <li>
                <p>(1.3) &mdash; Otherwise, <code>iterator_concept</code> denotes <code>input_iterator_tag</code>.</p>
              </li>
            </ol>
          </blockquote>
          <blockquote>
            <p>-2- The member <i>typedef-name</i> <code>iterator_category</code> is defined if and only if
              <code><i>mapped-is-glvalue</i></code> is <code>true</code>, <code><i>Base</i></code>
              models <code>forward_range</code>, and <code><i>MappedBase</i></code> models
              <code>forward_range</code>. In that case,
              <code>iterator::iterator_category</code> is defined as follows:
            </p>
            <ol style="list-style-type: none">
              <li>
                <p>(2.1) &mdash; Let <i>BASEC</i> denote
                  <code>iterator_traits&lt;iterator_t&lt;<i>Base</i>&gt;&gt;::iterator_category</code>, and
                  let <i>MAPPEDC</i> denote
                  <code>iterator_traits&lt;iterator_t&lt;<i>MappedBase</i>&gt;&gt;::iterator_category</code>.
                </p>
              </li>
              <li>
                <p>(2.2) &mdash; If <i>BASEC</i> and <i>MAPPEDC</i> each model
                  <code>derived_from&lt;bidirectional_iterator_tag&gt;</code> and
                  <code><i>MappedBase</i></code> models <code>common_range</code>,
                  <code>iterator_category</code> denotes
                  <code>bidirectional_iterator_tag</code>.
                </p>
              </li>
              <li>
                <p>(2.3) &mdash; Otherwise, if <i>BASEC</i> and <i>MAPPEDC</i> each model
                  <code>derived_from&lt;forward_iterator_tag&gt;</code>,
                  <code>iterator_category</code> denotes <code>forward_iterator_tag</code>.
                </p>
              </li>
              <li>
                <p>(2.4) &mdash; Otherwise, <code>iterator_category</code> denotes <code>input_iterator_tag</code>.</p>
              </li>
            </ol>
          </blockquote>
          <blockquote>
            <p>-3- <code>iterator::difference_type</code> denotes the type:</p>
            <pre>      common_type_t&lt;range_difference_t&lt;<i>Base</i>&gt;, range_difference_t&lt;<i>MappedBase</i>&gt;&gt;</pre>
          </blockquote>
          <blockquote>
            <p>-4- <code>flat_map_view</code> iterators use the <code><i>satisfy</i></code> function to skip over empty
              mapped ranges.</p>
          </blockquote>
          <pre>constexpr <i>BaseIter</i>& <i>current</i>();
constexpr const <i>BaseIter</i>& <i>current</i>() const;</pre>
          <blockquote>
            <p>-5- <i>Returns</i>: <code><i>current_</i></code> if <code><i>Base</i></code> models
              <code>forward_range</code>; otherwise,
              <code>*<i>parent_</i>-><i>current_</i></i></code>.
            </p>
          </blockquote>
          <pre>constexpr auto& <i>update-mapped</i>();</pre>
          <blockquote>
            <p>-6- <i>Effects</i>:</p>
            <ol style="list-style-type: none">
              <li>
                <p>
                  (6.1) &mdash; If <code><i>mapped-is-glvalue</i></code> is <code>true</code>, equivalent to:</p>
                <pre>
      <i>mapped_</i> = addressof(<i>as-lvalue</i>(std::invoke(*<i>parent_</i>-&gt;<i>fun_</i>, *<i>current</i>())));
      return *<i>mapped_</i>;</pre>
              </li>
              <li>
                <p>(6.1) &mdash; Otherwise, equivalent to:</p>
                <pre>      return <i>parent_</i>-&gt;<i>mapped_</i>.<i>emplace-def</i>(<i>map-iterator</i>(*<i>parent_</i>-&gt;<i>fun_</i>, <i>current</i>()));</pre>
                <p>where <code><i>map-iterator</i></code> is the exposition-only class:</p>
                <pre>
  class <i>map-iterator</i> {
    <i>maybe-const</i>&lt;Const, F&gt;& fun_;
    const <i>BaseIter</i>& current_;
    constexpr <i>map-iterator</i>(<i>maybe-const</i>&lt;Const, F&gt;& fun, const <i>BaseIter</i>& current)
      : fun_(fun), current_(current) {}
  public:
    constexpr auto operator*() const noexcept(noexcept(std::invoke(fun_, *current_))) {
      return std::invoke(fun_, *current_);
    }
  };
      </pre>
              </li>
              </pre>
          </blockquote>
          <pre>constexpr auto& <i>get-mapped</i>();</pre>
          <blockquote>
            <p>-7- <i>Effects</i>: Equivalent to:</p>
            <pre>
      if constexpr (<i>mapped-is-glvalue</i>)
        return *<i>mapped_</i>;
      else
        return *<i>parent_</i>-&gt;<i>mapped_</i>;</pre>
          </blockquote>
          <pre>constexpr void <i>satisfy</i>();</pre>
          <blockquote>
            <p>-8- <i>Effects</i>: Equivalent to:</p>
            <pre>
      for (; <i>current</i>() != ranges::end(<i>parent_</i>-><i>base_</i>); ++<i>current</i>()) {
        auto& mapped = <i>update-mapped</i>();
        <i>mapped_it_</i> = ranges::begin(mapped);
        if (*<i>mapped_it_</i> != ranges::end(mapped))
          return;
      }
      if constexpr (<i>mapped-is-glvalue</i>)
        <i>mapped_it_</i>.reset();
          </blockquote>
        </blockquote>
        <pre>constexpr <i>iterator</i>(<i>Parent</i>& parent, <i>BaseIter</i> current)
  requires forward_range&lt;<i>Base</i>&gt;;</pre>
            <blockquote>
              <p>-9- <i>Effects</i>: Initializes <code><i>current_</i></code> with <code>std::move(current)</code> and
                <code><i>parent_</i></code> with <code>addressof(parent)</code>; then calls
                <code><i>satisfy</i>()</code>.
              </p>
            </blockquote>
            <pre>constexpr explicit <i>iterator</i>(<i>Parent</i>& parent)
  requires (!forward_range&lt;<i>Base</i>&gt;);</pre>
            <blockquote>
              <p>-10- <i>Effects</i>: Initializes
                <code><i>parent_</i></code> with <code>addressof(parent)</code>; then calls
                <code><i>satisfy</i>()</code>.
              </p>
            </blockquote>
            <pre>template&lt;class MappedRng = invoke_result_t&lt;F&, range_reference_t&lt;V&gt;&gt;&gt;
  constexpr <i>iterator</i>(iterator&lt;!Const&gt; i)
    requires Const &&
             is_reference_v&lt;MappedRng&gt; &&
             convertible_to&lt;iterator_t&lt;V&gt;, <i>BaseIter</i>&gt; &&
             convertible_to&lt;iterator_t&lt;MappedRng&gt;, <i>MappedIter</i>&gt; &&
             convertible_to&lt;add_pointer_t&lt;MappedRng&gt;, add_pointer_t&lt;<i>MappedBase</i>&gt;&gt;;</pre>
            <blockquote>
              <p>-11- <i>Effects</i>: Initializes
                <code><i>current_</i></code> with <code>std::move(i.<i>current_</i>)</code>,
                <code><i>mapped_it_</i></code> with <code>std::move(i.<i>mapped_it_</i>)</code>,
                <code><i>parent_</i></code> with <code>i.<i>parent_</i></code>,
                and <code><i>mapped_</i></code> with <code>i.<i>mapped_</i></code>.
              </p>
              <p>-12- [<i>Note 1</i>: <code>Const</code> can only be <code>true</code> when <code><i>Base</i></code>
                models <code>forward_range</code>. — <i>end note</i>]</p>
            </blockquote>
            <pre>constexpr <i>MappedIter</i> operator-&gt;() const
  requires <i>has-arrow</i>&lt;<i>MappedIter</i>&gt; && copyable&lt;<i>MappedIter</i>&gt;;</pre>
            <blockquote>
              <p>-13- <i>Effects</i>: Equivalent to: <code>return *<i>mapped_it_</i>;</code>
              </p>
            </blockquote>
            <pre>constexpr <i>iterator</i>& operator++();</pre>
            <blockquote>
              <p>-14- <i>Effects</i>: Equivalent to:</p>
              <pre>
      if (++*<i>mapped_it_</i> == ranges::end(<i>get-mapped</i>())) {
        ++<i>current</i>();
        <i>satisfy</i>();
      }
      return *this;
              </pre>
            </blockquote>
            <pre>constexpr void operator++(int);</pre>
            <blockquote>
              <p>-15- <i>Effects</i>: Equivalent to: <code>++*this</code>.
              </p>
            </blockquote>
            <pre>constexpr <i>iterator</i> operator++(int)
  requires <i>mapped-is-glvalue</i> && forward_range&lt;<i>Base</i>&gt; &&
           forward_range&lt;<i>MappedBase</i>&gt;;</pre>
            <blockquote>
              <p>-16- <i>Effects</i>: Equivalent to:
              </p>
              <pre>
      auto tmp = *this;
      ++*this;
      return tmp;
</pre>
            </blockquote>
            <pre>constexpr <i>iterator</i>& operator--()
  requires <i>mapped-is-glvalue</i> && bidirectional_range&lt;<i>Base</i>&gt; &&
           <i>bidirectional-common</i>&lt;<i>MappedBase</i>&gt;;</pre>
            <blockquote>
              <p>-17- <i>Effects</i>: Equivalent to:
              </p>
              <pre>
      if (<i>current_</i> == ranges::end(<i>parent_</i>-&gt;<i>base_</i>)) {
        --<i>current_</i>;
        <i>mapped_it_</i> = ranges::end(<i>update-mapped</i>());
      }
      
      while (<i>mapped_it_</i> == ranges::begin(<i>get-mapped()</i>)) {
        --<i>current_</i>;
        <i>mapped_it_</i> = ranges::end(<i>update-mapped</i>());
      }
      --*<i>mapped_it_</i>;
      return *this;
</pre>
            </blockquote>
            <pre>constexpr <i>iterator</i> operator--(int)
  requires <i>mapped-is-glvalue</i> && bidirectional_range&lt;<i>Base</i>&gt; &&
           <i>bidirectional-common</i>&lt;<i>MappedBase</i>&gt;;</pre>
            <blockquote>
              <p>-18- <i>Effects</i>: Equivalent to:
              </p>
              <pre>
      auto tmp = *this;
      --*this;
      return tmp;
</pre>
            </blockquote>
            <pre>friend constexpr bool operator==(const <i>iterator</i>& x, const <i>iterator</i>& y)
  requires <i>mapped-is-glvalue</i> && forward_range&lt;<i>Base</i>&gt; &&
           equality_comparable&lt;iterator_t&lt;<i>MappedBase</i>&gt;&gt;;</pre>
            <blockquote>
              <p>-19- <i>Effects</i>: Equivalent to:
                <code>return x.<i>current_</i> == y.<i>current_</i> && x.<i>mapped_it_</i> == y.<i>mapped_it_</i>;</code>
              </p>
            </blockquote>
            <pre>friend constexpr void iter_swap(const <i>iterator</i>& x, const <i>iterator</i>& y)
  noexcept(noexcept(ranges::iter_swap(*x.<i>mapped_it_</i>, *y.<i>mapped_it_</i>)))
  requires indirectly_swappable&lt;<i>MappedIter</i>&gt;;</pre>
            <blockquote>
              <p>-20- <i>Effects</i>: Equivalent to:
                <code>ranges::iter_swap(*x.<i>mapped_it_</i>, *y.<i>mapped_it_)</i>;</code>
              </p>
            </blockquote>
            <p>[25.7.?.3] Class template <code>flat_map_view::<i>sentinel</i></code> [range.flat.map.sentinel]</p>
            <pre>
namespace std::ranges {
  template&lt;input_range V, move_constructible F&gt; 
    requires view&lt;V&gt; && is_object_v&lt;F&gt; && <i>flat-mappable</i>&lt;V, F&gt;
  template&lt;bool Const&gt;
  struct flat_map_view&lt;V, F&gt;::<i>sentinel</i> {
  private:
    using <i>Parent</i> = <i>maybe-const</i>&lt;Const, flat_map_view&gt;;     // <i>exposition only</i>
    using <i>Base</i> = <i>maybe-const</i>&lt;Const, V&gt;;                   // <i>exposition only</i>
    sentinel_t&lt;<i>Base</i>&gt; <i>end_</i> = sentinel_t&lt;<i>Base</i>&gt;();           // <i>exposition only</i>

  public:
    <i>sentinel</i>() = default;

    constexpr explicit <i>sentinel</i>(<i>Parent</i>& parent);
    constexpr <i>sentinel</i>(<i>sentinel</i>&lt;!Const&gt; s)
      requires Const && convertible_to&lt;sentinel_t&lt;V&gt;, sentinel_t&lt;<i>Base</i>&gt;&gt;;

    template&lt;bool OtherConst&gt;
      requires sentinel_for&lt;sentinel_t&lt;<i>Base</i>&gt;, iterator_t&lt;<i>maybe-const</i>&lt;OtherConst, V&gt;&gt;&gt;
    friend constexpr bool operator==(const iterator&lt;OtherConst&gt;& x, const <i>sentinel</i>& y);
  };
}</pre>
            <pre>constexpr explicit <i>sentinel</i>(<i>Parent</i>& parent);</pre>
            <blockquote>
              <p>-1- <i>Effects</i>: Initializes <code><i>end_</i></code> with
                <code>ranges::end(parent.<i>base_</i>)</code>.
              </p>
            </blockquote>
            <pre>constexpr <i>sentinel</i>(<i>sentinel</i>&lt;!Const&gt; s)
  requires Const && convertible_to&lt;sentinel_t&lt;V&gt;, sentinel_t&lt;<i>Base</i>&gt;&gt;;</pre>
            <blockquote>
              <p>-2- <i>Effects</i>: Initializes <code><i>end_</i></code> with
                <code>std::move(s.<i>end_</i>)</code>.
              </p>
            </blockquote>
            <pre>template&lt;bool OtherConst&gt;
  requires sentinel_for&lt;sentinel_t&lt;<i>Base</i>&gt;, iterator_t&lt;<i>maybe-const</i>&lt;OtherConst, V&gt;&gt;&gt;
friend constexpr bool operator==(const iterator&lt;OtherConst&gt;& x, const <i>sentinel</i>& y);</pre>
            <blockquote>
              <p>-3- <i>Effects</i>: Equivalent to: <code>return x.<i>current</i>() == y.<i>end_</i>;</code>
              </p>
      </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-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>
</body>

</html>