<title>Making Your Own Container Compatible With C++20 Ranges</title>
<link>https://www.reedbeta.com/blog/ranges-compatible-containers/</link>
<guid>https://www.reedbeta.com/blog/ranges-compatible-containers/</guid>
<dc:creator>Nathan Reed</dc:creator>
<pubDate>Sat, 20 Mar 2021 17:23:15 -0700</pubDate>
<comments>https://www.reedbeta.com/blog/ranges-compatible-containers/#comments</comments>
<category>Coding</category>
<description><p>With some of my spare time lately, I’ve been enjoying learning about some of the new features in
C++20. <a href="https://en.cppreference.com/w/cpp/language/constraints">Concepts</a> and the closely-related
<a href="https://akrzemi1.wordpress.com/2020/03/26/requires-clause/"><code>requires</code> clauses</a> are two great
extensions to template syntax that remove the necessity for all the SFINAE junk we used to have to
do, making our code both more readable and more precise, and providing much better error messages
(although MSVC has sadly been <a href="https://developercommunity.visualstudio.com/t/786814">lagging in the error messages department</a>,
at the time of this writing).</p>
<p>Another interesting C++20 feature is the addition of the <a href="https://en.cppreference.com/w/cpp/ranges">ranges library</a>
(also <a href="https://en.cppreference.com/w/cpp/algorithm/ranges">ranges algorithms</a>), which provides a
nicer, more composable abstraction for operating on containers and sequences of objects. At the most
basic level, a range wraps an iterator begin/end pair, but there’s much more to it than that. This
article isn’t going to be a tutorial on ranges, but <a href="https://www.youtube.com/watch?v=VmWS-9idT3s">here’s a talk</a>
to watch if you want to see more of what it’s all about.</p>
<p>What I’m going to discuss today is the process of adding “ranges compatibility” to your own container
class. Many of the C++ codebases we work in have their own set of container classes beyond the STL
ones, for a variety of reasons—<a href="/blog/data-oriented-hash-table/">better performance</a>, more control
over memory layouts, more customized interfaces, and so on. With a little work, it’s possible to
make your custom containers also function as ranges and interoperate with the C++20 ranges library.
Here’s how to do it.</p>
<!--more-->
<div class="toc">
<ul>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#making-your-container-an-input-range">Making Your Container an Input Range</a><ul>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#range-concepts">Range Concepts</a></li>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#defining-range-compatible-iterators">Defining Range-Compatible Iterators</a></li>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#begin-end-size">Begin, End, Size</a></li>
</ul>
</li>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#accepting-output-from-ranges">Accepting Output From Ranges</a><ul>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#constructor-from-a-range">Constructor From A Range</a></li>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#output-iterators">Output Iterators</a></li>
</ul>
</li>
<li><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#conclusion">Conclusion</a></li>
</ul>
</div>
<h2 id="making-your-container-an-input-range"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#making-your-container-an-input-range" title="Permalink to this section">Making Your Container an Input Range</a></h2>
<p>At the high level, there are two basic ways that a container class can interact with ranges. First,
it can be <em>readable</em> as a range, meaning that we can iterate over it, pipe it into views and pass it
to range algorithms, and so forth. In the parlance of the ranges library, this is known as being an
<em>input range</em>: a range that can provide input to other things.</p>
<p>The other direction is to accept output <em>from</em> ranges, storing the output into your container.
We’ll do that later. To begin with, let’s see how to make your container act as an input range.</p>
<h3 id="range-concepts"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#range-concepts" title="Permalink to this section">Range Concepts</a></h3>
<p>The first decision we have to make is what particular kind of input range we can model. The C++20
STL defines a number of different <a href="https://en.cppreference.com/w/cpp/ranges#Range_concepts">concepts for ranges</a>,
depending on the capabilities of their iterators and other things. Several of these form a hierarchy
from more general to more specific kinds of ranges with tighter requirements. Generally speaking, it’s
best for your container to implement the most specific range concept it’s able to. This enables code
that works with ranges to make better decisions and use more optimal code paths. (We’ll see some
examples of this in a minute.)</p>
<p>The relevant input range concepts are:</p>
<ul>
<li><code>std::ranges::input_range</code>: the most bare-bones version. It requires only that you have iterators
that can retrieve the contents of the range. In particular, it <em>doesn’t</em> require that the range
can be iterated more than once: iterators are not required to be copyable, and <code>begin</code>/<code>end</code> are
not required to give you the iterators more than once. This could be an appropriate concept for
ranges that are actually generating their contents as the result of some algorithm that’s not
easily/cheaply repeatable, or receiving data from a network connection or suchlike.</li>
<li><code>std::ranges::forward_range</code>: the range can be iterated as many times as you like, but only in
the forward direction. Iterators can be copied and saved off to later resume iteration from an
earlier point, for example.</li>
<li><code>std::ranges::bidirectional_range</code>: iterators can be decremented as well as incremented.</li>
<li><code>std::ranges::random_access_range</code>: you can efficiently do arithmetic on iterators—you can
offset them forward or backward by a given number of steps, or subtract them to find the number
of steps between.</li>
<li><code>std::ranges::contiguous_range</code>: the elements are actually stored as a contiguous array in memory;
the iterators are essentially fancy pointers (or literally <em>are</em> just pointers).</li>
</ul>
<p>In addition to this hierarchy of input range concepts, there are a couple of other standalone ones
worth mentioning:</p>
<ul>
<li><code>std::ranges::sized_range</code>: you can efficiently get the size of the range, i.e. how many elements
from begin to end. Note that this is a much looser constraint than <code>random_access_range</code>: the
latter requires you be able to efficiently measure the distance between <em>any pair</em> of iterators
inside the range, while <code>sized_range</code> only requires that the size of the <em>whole range</em> is known.</li>
<li><code>std::ranges::borrowed_range</code>: indicates that a range doesn’t own its data, i.e. it’s referencing
(“borrowing”) data that lives somewhere else. This can be useful because it allows references/iterators
into the data to survive beyond the lifetime of the range object itself.</li>
</ul>
<p>The reason all these concepts are important is that if I’m writing code that operates on ranges, I might need to
require some of these concepts in order to do my work efficiently. For example, a sorting routine
would be very difficult to write for anything less than a <code>random_access_range</code> (and indeed you’ll
see that <a href="https://en.cppreference.com/w/cpp/algorithm/ranges/sort"><code>std::ranges::sort</code> requires that</a>).
In other cases, I might be able to do things more optimally when the range satisfies certain
concepts—for instance, if it’s a <code>sized_range</code>, I could preallocate some storage for results,
while if it’s only an <code>input_range</code> and no more, then I’ll have to dynamically reallocate, as I have
no idea how many elements there are going to be.</p>
<p>The rest of the ranges library is written in terms of these concepts (and you can write your own
code that operates generically on ranges using these concepts as well). So, once your container
satisfies the relevant concepts, it will automatically be recognized and function as a range!</p>
<p>In C++20, concepts act as boolean expressions, so you can check whether your container satisfies the
concepts you expect by just writing asserts for them:</p>
<div class="codehilite"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf"><ranges></span>
<span class="k">static_assert</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">forward_range</span><span class="o"><</span><span class="n">MyCoolContainer</span><span class="o"><</span><span class="kt">int</span><span class="o">>></span><span class="p">);</span>
<span class="c1">// int is just an arbitrarily chosen element type, since we</span>
<span class="c1">// can't assert a concept for an uninstantiated template</span>
</code></pre></div>
<p>Checks like this are great to add to your test suite—I’m big in favor of writing <em>compile-time</em>
tests for generic/metaprogramming stuff, in addition to the usual runtime tests.</p>
<p>However, when you first drop that assert into your code, it will almost certainly fail. Let’s see
now what you need to do to actually satisfy the range concepts.</p>
<h3 id="defining-range-compatible-iterators"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#defining-range-compatible-iterators" title="Permalink to this section">Defining Range-Compatible Iterators</a></h3>
<p>In order to satisfy the input range concepts, you need to do two things:</p>
<ul>
<li>Have <code>begin</code> and <code>end</code> functions that return some iterator and sentinel types. (We’ll discuss
these in a little bit.)</li>
<li>The iterator type must satisfy the iterator concept that matches your range concept.</li>
</ul>
<p>Each one of the concepts from <code>input_range</code> down to <code>contiguous_range</code> has a corresponding
<a href="https://en.cppreference.com/w/cpp/header/iterator#Iterator_concepts">iterator concept</a>:
<code>std::input_iterator</code>, <code>std::forward_iterator</code>, and so on. It’s these concepts that contain the real
meat of the requirements that define the different types of ranges: they list all the operations
each kind of iterator must support.</p>
<p>To begin with, there are a couple of member type aliases that any iterator class will need to define:</p>
<ul>
<li><code>difference_type</code>: some signed integer type, usually <code>std::ptrdiff_t</code></li>
<li><code>value_type</code>: the type of elements that the iterator references</li>
</ul>
<p>The second one seems pretty understandable, but I honestly have no idea why the <code>difference_type</code>
requirement is here. Taking the difference between iterators doesn’t make sense until you get to
random-access iterators, which actually define that operation. As far as I can tell, the
<code>difference_type</code> for more general iterators isn’t actually <em>used</em> by anything. Nevertheless,
according to the C++ standard, it has to be there. It seems that the usual idiom is to set it to
<code>std::ptrdiff_t</code> in such cases, although it can be any signed integer type.</p>
<p>(Technically you can also define these types by specializing <code>std::iterator_traits</code> for your iterator,
but here we’re just going to put them in the class.)</p>
<p>Beyond that, the requirements for <code>std::input_iterator</code> are pretty straightforward:</p>
<ul>
<li>The iterator must be default-initializable and movable. (It doesn’t have to be copyable.)</li>
<li>It must be equality-comparable with its sentinel (the value marking the end of the range). It
doesn’t have to be equality-comparable with other iterators.</li>
<li>It must implement <code>operator ++</code>, in <em>both</em> preincrement and postincrement positions. However, the
postincrement version does not have to return anything.</li>
<li>It must have an <code>operator *</code> that returns a reference to whatever the <code>value_type</code> is.</li>
</ul>
<p>One point of interest here is that the default-initializable requirement means that the iterator class
can’t contain references, e.g. a reference to the container it comes from. It can store pointers,
though.</p>
<p>A prototype input iterator class could look like this:</p>
<div class="codehilite"><pre><span></span><code><span class="k">template</span><span class="w"> </span><span class="o"><</span><span class="k">typename</span><span class="w"> </span><span class="nc">T</span><span class="o">></span>
<span class="k">class</span><span class="w"> </span><span class="nc">Iterator</span>
<span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
<span class="w"> </span><span class="k">using</span><span class="w"> </span><span class="n">difference_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="kt">ptrdiff_t</span><span class="p">;</span>
<span class="w"> </span><span class="k">using</span><span class="w"> </span><span class="n">value_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">T</span><span class="p">;</span>
<span class="w"> </span><span class="n">Iterator</span><span class="p">();</span><span class="w"> </span><span class="c1">// default-initializable</span>
<span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="k">operator</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">Sentinel</span><span class="o">&</span><span class="p">)</span><span class="w"> </span><span class="k">const</span><span class="p">;</span><span class="w"> </span><span class="c1">// equality with sentinel</span>
<span class="w"> </span><span class="n">T</span><span class="o">&</span><span class="w"> </span><span class="k">operator</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="p">;</span><span class="w"> </span><span class="c1">// dereferenceable</span>
<span class="w"> </span><span class="n">Iterator</span><span class="o">&</span><span class="w"> </span><span class="k">operator</span><span class="w"> </span><span class="o">++</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="c1">// pre-incrementable</span>
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cm">/*do stuff...*/</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">*</span><span class="k">this</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="k">operator</span><span class="w"> </span><span class="o">++</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="c1">// post-incrementable</span>
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">++*</span><span class="k">this</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="k">private</span><span class="o">:</span>
<span class="w"> </span><span class="c1">// implementation...</span>
<span class="p">};</span>
</code></pre></div>
<p>For a <code>std::forward_iterator</code>, the requirements are just slightly tighter:</p>
<ul>
<li>The iterator must be copyable.</li>
<li>It must be equality-comparable with other iterators of the same container.</li>
<li>The postincrement operator must return a copy of the iterator before modification.</li>
</ul>
<p>A prototype forward iterator class could look like:</p>
<div class="codehilite"><pre><span></span><code><span class="k">template</span><span class="w"> </span><span class="o"><</span><span class="k">typename</span><span class="w"> </span><span class="nc">T</span><span class="o">></span>
<span class="k">class</span><span class="w"> </span><span class="nc">Iterator</span>
<span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
<span class="w"> </span><span class="c1">// ...same as the previous one, except:</span>
<span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="k">operator</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">Iterator</span><span class="o">&</span><span class="p">)</span><span class="w"> </span><span class="k">const</span><span class="p">;</span><span class="w"> </span><span class="c1">// equality with iterators</span>
<span class="w"> </span><span class="n">Iterator</span><span class="w"> </span><span class="k">operator</span><span class="w"> </span><span class="o">++</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="c1">// post-incrementable, returns prev value</span>
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Iterator</span><span class="w"> </span><span class="n">temp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="k">this</span><span class="p">;</span><span class="w"> </span><span class="o">++*</span><span class="k">this</span><span class="p">;</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">temp</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>I’m not going to go through the rest of them in detail; you can read the details
<a href="https://en.cppreference.com/w/cpp/header/iterator#Iterator_concepts">on cppreference</a>.</p>
<h3 id="begin-end-size"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#begin-end-size" title="Permalink to this section">Begin, End, Size</a></h3>
<p>Once your container is equipped with an iterator class that satisfies the relevant concepts, you’ll
need to provide <code>begin</code> and <code>end</code> functions to get those iterators. There are three ways to do this:
they can be member functions on the container, they can be free functions that live next to the
container in the same namespace, or they can be <a href="https://www.justsoftwaresolutions.co.uk/cplusplus/hidden-friends.html">“hidden friends”</a>;
they just need to be findable by <a href="https://en.cppreference.com/w/cpp/language/adl">ADL</a>.</p>
<p>The return types from <code>begin</code> and <code>end</code> don’t have to be the same. In some cases, it can be useful
to have <code>end</code> return a different type of object, a “sentinel”, which isn’t actually an iterator; it
just needs to be equality-comparable with iterators, so you can tell when you’ve gotten to the end
of the container.</p>
<p>Also, these are the same <code>begin</code>/<code>end</code> used for <a href="https://en.cppreference.com/w/cpp/language/range-for">range-based <code>for</code> loops</a>.</p>
<p>One oddity worth mentioning here is that if you go the free/friend functions route, you’ll need to
add overloads for both const and non-const versions of your container:</p>
<div class="codehilite"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">MyCoolContainer</span><span class="p">;</span>
<span class="k">auto</span><span class="w"> </span><span class="n">begin</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="o">&</span><span class="w"> </span><span class="n">c</span><span class="p">);</span>
<span class="k">auto</span><span class="w"> </span><span class="n">end</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="o">&</span><span class="w"> </span><span class="n">c</span><span class="p">);</span>
<span class="k">auto</span><span class="w"> </span><span class="n">begin</span><span class="p">(</span><span class="n">MyCoolContainer</span><span class="o">&</span><span class="w"> </span><span class="n">c</span><span class="p">);</span>
<span class="k">auto</span><span class="w"> </span><span class="n">end</span><span class="p">(</span><span class="n">MyCoolContainer</span><span class="o">&</span><span class="w"> </span><span class="n">c</span><span class="p">);</span>
</code></pre></div>
<p>You might think it would be enough to provide just the const overloads, but if you do that, only the
const version of the container will be recognized as a range! The non-const overloads must be
present as well for non-const containers to work.</p>
<p>Curiously, if you provide <code>begin</code>/<code>end</code> as member functions instead, then this doesn’t come up:
const overloads will work for both.</p>
<p>This behavior is surprising, and I’m not sure if it was intended. However, it’s worth noting that
iterators generally need to remember the constness of the container they came from: a const
container should give you a “const iterator” that doesn’t allow mutating its elements. Therefore,
the const and non-const overloads of <code>begin</code>/<code>end</code> will generally need to return <em>different</em>
iterator types, and so you’ll need to have both in any case. (The exception would be if you’re
building an immutable container; then it only needs a const iterator type.)</p>
<p>In addition to <code>begin</code> and <code>end</code>, you’ll also want to implement a <code>size</code> function, if applicable.
Again, this can be either a member function, a free function, or a hidden friend. The
presence of this function satisfies <code>std::ranges::sized_range</code>, which (as mentioned earlier) can
enable range algorithms to operate more efficiently.</p>
<p>So, to sum up: to allow your custom container class to be readable as a range, you’ll need to:</p>
<ol>
<li>Decide which range concept(s) you can model, which mainly comes down to what level of iterator
capabilities you can provide;</li>
<li>Implement iterator classes (both const and non-const, if applicable) that fulfill all the
requirements of the chosen iterator concept;</li>
<li>Implement <code>begin</code>, <code>end</code>, and <code>size</code> functions.</li>
</ol>
<p>Once we’ve done this, the ranges library should recognize your container as a range. It will
automatically be accepted by range algorithms, we can take views of it, we can iterate over it in
range-for loops, and so on.</p>
<p>As before, you can test that you’ve done everything correctly by asserting that your container
satisfies the expected range concepts. If you’re working with gcc or clang, this will even give you
some pretty reasonable error messages if you didn’t get it right! (In MSVC, for the time being, you’ll
have to narrow down errors by popping open the hood and asserting each of the concept’s sub-clauses
one at a time, to see which one(s) failed.)</p>
<h2 id="accepting-output-from-ranges"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#accepting-output-from-ranges" title="Permalink to this section">Accepting Output From Ranges</a></h2>
<p>We’ve discussed how to make a custom container serve as input <em>to</em> the C++20 ranges library. Now, we
need to come back to the other direction: how to let your container capture output <em>from</em> the
ranges library.</p>
<p>There are a couple of different forms this can take. One way is to accept generic ranges as
parameters to a constructor (or other methods, such as append or insert methods) of your container
class. This allows, for example, easily converting other containers (that are also range-compatible)
to your container. It also allows capturing the output of a ranges “pipeline” (a series of views
chained together).</p>
<p>Another form of range output, which comes up with certain of the <a href="https://en.cppreference.com/w/cpp/algorithm/ranges">range algorithms</a>,
is via <em>output iterators</em>, which are iterators that allow storing or inserting values into your
container.</p>
<h3 id="constructor-from-a-range"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#constructor-from-a-range" title="Permalink to this section">Constructor From A Range</a></h3>
<p>To write a constructor (or other method) that takes a generic range parameter, we can use the same
range concepts we saw earlier. One neat new feature in C++20 is writing functions with a parameter
type (or return type) constrained to match a given concept. The syntax looks like this:</p>
<div class="codehilite"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf"><ranges></span>
<span class="k">class</span><span class="w"> </span><span class="nc">MyCoolContainer</span>
<span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
<span class="w"> </span><span class="k">explicit</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">input_range</span><span class="w"> </span><span class="k">auto</span><span class="o">&&</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">auto</span><span class="o">&&</span><span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// process the item</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>The syntax <code>concept-name auto</code> for the parameter type reminds us that concepts aren’t types; this
is still, under the hood, a template function that’s performing argument type deduction (hence the
<code>auto</code>). In other words, the above is syntactic sugar for:</p>
<div class="codehilite"><pre><span></span><code><span class="k">template</span><span class="w"> </span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">input_range</span><span class="w"> </span><span class="n">R</span><span class="o">></span>
<span class="k">explicit</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="p">(</span><span class="n">R</span><span class="o">&&</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div>
<p>which is in turn sugar for:</p>
<div class="codehilite"><pre><span></span><code><span class="k">template</span><span class="w"> </span><span class="o"><</span><span class="k">typename</span><span class="w"> </span><span class="nc">R</span><span class="o">></span>
<span class="k">requires</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">input_range</span><span class="o"><</span><span class="n">R</span><span class="o">></span><span class="p">)</span>
<span class="k">explicit</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="p">(</span><span class="n">R</span><span class="o">&&</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div>
<p>I prefer the shorthand <code>std::ranges::input_range auto</code> syntax, but <del>at the time of this writing
MSVC’s support for it is still shaky</del>. (<em>Update: fixed in 16.10!</em> 😊) If in doubt, use
the syntax <code>template <std::ranges::input_range R></code>.</p>
<p>In any case, constraining the parameter type to satisfy <code>input_range</code> allows this constructor
overload to accept anything out there that implements <code>begin</code>, <code>end</code>, and iterators, as we’ve seen
in previous sections. You can then iterate over it generically and do whatever you want with the
results.</p>
<p>The range parameter is declared as <code>auto&&</code> to make it a <a href="https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers">universal reference</a>,
meaning that it can accept either lvalues or rvalues; in particular, it can accept the result of a
function call returning a range, and it can accept the result of a pipeline:</p>
<div class="codehilite"><pre><span></span><code><span class="n">MyCoolContainer</span><span class="w"> </span><span class="n">c</span><span class="p">{</span><span class="w"> </span><span class="n">another_range</span><span class="w"> </span><span class="o">|</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">views</span><span class="o">::</span><span class="n">transform</span><span class="p">(</span><span class="n">blah</span><span class="p">)</span><span class="w"> </span><span class="o">|</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">views</span><span class="o">::</span><span class="n">filter</span><span class="p">(</span><span class="n">blah</span><span class="p">)</span><span class="w"> </span><span class="p">};</span>
</code></pre></div>
<p>A completely generic range-accepting method like this might not be the most useful thing. If we have
a container storing <code>int</code> values, for example, it wouldn’t make a lot of sense for us to accept
ranges of strings or other arbitrary types. We’d like to be able to put some additional constraints
on the <em>element type</em> of the range: perhaps we only want element types that are convertible to <code>int</code>.</p>
<p>Helpfully, the ranges library provides a template <a href="https://en.cppreference.com/w/cpp/ranges/iterator_t"><code>range_value_t</code></a>
that retrieves the element type of a range—namely, the <code>value_type</code> declared by the range’s
iterator. With this, we can state additional constraints like so:</p>
<div class="codehilite"><pre><span></span><code><span class="k">explicit</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">input_range</span><span class="w"> </span><span class="k">auto</span><span class="o">&&</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="k">requires</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">convertible_to</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">range_value_t</span><span class="o"><</span><span class="k">decltype</span><span class="p">(</span><span class="n">range</span><span class="p">)</span><span class="o">></span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="o">></span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div>
<p>We can even define a concept that wraps up these requirements:</p>
<div class="codehilite"><pre><span></span><code><span class="k">template</span><span class="w"> </span><span class="o"><</span><span class="k">typename</span><span class="w"> </span><span class="nc">R</span><span class="p">,</span><span class="w"> </span><span class="k">typename</span><span class="w"> </span><span class="nc">T</span><span class="o">></span>
<span class="k">concept</span><span class="w"> </span><span class="nc">input_range_of</span><span class="w"> </span><span class="o">=</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">input_range</span><span class="o"><</span><span class="n">R</span><span class="o">></span><span class="w"> </span><span class="o">&&</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">convertible_to</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">range_value_t</span><span class="o"><</span><span class="n">R</span><span class="o">></span><span class="p">,</span><span class="w"> </span><span class="n">T</span><span class="o">></span><span class="p">;</span>
</code></pre></div>
<p>and then use it as follows:</p>
<div class="codehilite"><pre><span></span><code><span class="k">explicit</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="p">(</span><span class="n">input_range_of</span><span class="o"><</span><span class="kt">int</span><span class="o">></span><span class="w"> </span><span class="k">auto</span><span class="o">&&</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div>
<p>Something like this should be in the standard library, IMO.</p>
<p>You can also choose to require one of the more specialized concepts, like <code>forward_range</code> or
<code>random_access_range</code>, if you need those extra capabilities for whatever you’re doing.
However, just as a container should generally implement the most <em>specific</em> range concept it can
provide, a function that takes a range parameter should generally require the most <em>general</em> range
concept it can deal with, or it will unduly restrict what kind of ranges can be passed to it.</p>
<p>That said, there might be cases where you can switch to a more efficient implementation if the range
satisfies some extra requirements. For example, if it’s a <code>sized_range</code>, then you might be able to
reserve storage before inserting the elements. You can test for this inside your function body using
<code>if constexpr</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="k">explicit</span><span class="w"> </span><span class="n">MyCoolContainer</span><span class="p">(</span><span class="n">input_range_of</span><span class="o"><</span><span class="kt">int</span><span class="o">></span><span class="w"> </span><span class="k">auto</span><span class="o">&&</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">constexpr</span><span class="w"> </span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">sized_range</span><span class="o"><</span><span class="k">decltype</span><span class="p">(</span><span class="n">range</span><span class="p">)</span><span class="o">></span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">reserve</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">size</span><span class="p">(</span><span class="n">range</span><span class="p">));</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">auto</span><span class="o">&&</span><span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">range</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// process the item</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Here, <a href="https://en.cppreference.com/w/cpp/ranges/size"><code>std::ranges::size</code></a> is a convenience wrapper
that knows how to call the range’s associated <code>size</code> function, whether it’s implemented as a method
or a free function.</p>
<p>You could also do things like: check if the range is a <code>contiguous_range</code> and the item is something
trivially copyable, and switch to <code>memcpy</code> rather than iterating over all the items.</p>
<h3 id="output-iterators"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#output-iterators" title="Permalink to this section">Output Iterators</a></h3>
<p>Range views and pipelines operate on a “pull” model, where the pipeline is represented by a proxy
range object that generates its results lazily when you iterate it. Taking generic range objects as
parameters to your container is an easy and useful way to consume such objects, and that probably suffices
for most uses. However, there are a handful of bits in the ranges library that operate on a “push”
model, where you call a function that wants to store values into your container via an output
iterator. This comes up with <a href="https://en.cppreference.com/w/cpp/algorithm/ranges#Modifying_sequence_operations">certain ranges algorithms</a>
like <code>ranges::copy</code>, <code>ranges::transform</code>, and <code>ranges::generate</code>.</p>
<p>Personally, I don’t see a hugely compelling reason to worry about these, as it’s also possible to
use views to express the same operations; but for the sake of completeness, I’ll discuss them
briefly here.</p>
<p>At this point, it won’t surprise you to learn that just as there were concepts for input ranges,
there are also concepts <code>std::ranges::output_range</code> and <a href="https://en.cppreference.com/w/cpp/iterator/output_iterator"><code>std::output_iterator</code></a>.
In this case there’s just that one concept, not a hierarchy of refinements of them; however, if you
peruse the definitions of some of the ranges algorithms, you’ll find that many of them don’t actually
use <code>output_iterator</code>, but state slightly different, less- or more-specific requirements of their
own. (This part of the standard library feels a little less fully baked than the rest; I wouldn’t be
surprised if some of this gets elaborated or polished a bit more in C++23 or later revisions.)</p>
<p>The requirements for an output iterator (broadly construed) are very similar to those for an input
iterator, only adding that the value returned by dereferencing the iterator must be writable by
assigning to it: you must be able to do <code>*iter = foo;</code> for some appropriate type of <code>foo</code>. If you’ve
implemented a non-const input iterator, it probably satisfies the requirement already.</p>
<p>It’s also possible to do slightly more exotic things with an output iterator, like returning a proxy
object that accepts assignment and does “something” with the value assigned. An example of this is
the STL’s <a href="https://en.cppreference.com/w/cpp/iterator/back_insert_iterator"><code>std::back_insert_iterator</code></a>,
which takes whatever is assigned to it and <em>appends</em> to its container (as opposed to overwriting an
existing value in the container). The STL has a few more things like that, including an iterator
that writes characters out to an <code>ostream</code>.</p>
<p>There are also some cases amongst the ranges algorithms of “input-output” iterators, such as for
operations that reorder a range in place, like sorting. These often have a bidirectional or
random-access iterator requirement, plus needing the dereferenced types to be swappable, movable,
and varying other constraints. Those details probably aren’t going to be relevant to you unless
you’re doing something tricky, like making a container that generates elements on the fly somehow,
or returns proxy objects rather than direct references to elements (like <code>std::vector<bool></code>).</p>
<h2 id="conclusion"><a href="https://www.reedbeta.com/blog/ranges-compatible-containers/#conclusion" title="Permalink to this section">Conclusion</a></h2>
<p>The C++20 ranges library provides a lot of powerful, composable tools for manipulating sequences of
objects, and a range of specificity from the most generic and abstract container-shaped things down
to the very concrete, efficient, and practical. When working with your own container types, it
would be nice to be able to take advantage of these tools.</p>
<p>As we’ve seen, it’s hardly an onerous task to implement ranges compatibility for your own containers.
Most of the necessaries are things you were probably already doing: you probably already had an
iterator class and begin/end methods. It only takes a little bit of attention to satisfying certain
details—like adding the <code>difference_type</code> and <code>value_type</code> aliases, and making sure you can both
preincrement and postincrement—to make your iterators satisfy the STL iterator concepts, and thus
have your containers recognized as ranges. It’s also no sweat to write functions accepting generic
ranges as input, letting you store the output of other range operations into your container.</p>
<p>I hope this has been a useful peek under the hood and has given you some ideas about how your
container classes can benefit from the new C++20 features.</p></description>