Document number: P0022R1
Date: 2015-11-06
Project: Programming Language C++, Library Working Group
Reply-to: Eric Niebler <eniebler@boost.org>,

“Never do that by proxy which you can do yourself.”

Italian proverb

1 Introduction

This paper presents an extension to the Ranges design[4] that makes proxy iterators full-fledged members of the STL iterator hierarchy. This solves the “vector<bool>-is-not-a-container” problem along with several other problems that become apparent when working with range adaptors. It achieves this without fracturing the Iterator concept hierarchy[1] and without breaking iterators apart into separate traversal and access pieces[3].

The design presented makes only local changes to some Iterator concepts, fixes some existing library issues, and fills in a gap left by the addition of move semantics. To wit: the functions std::swap and std::iter_swap have been part of C++ since C++98. With C++11, the language got move semantics and std::move, but not std::iter_move. This arguably is an oversight. By adding it and making both iter_swap and iter_move customization points, iterators can control how elements are swapped and moved by permuting algorithms.

Also, there are outstanding issues in common_type, and by ignoring top-level cv- and ref-qualifiers, the trait is not as general as it could be. By fixing the issues and adding a new trait – common_reference – that respects cv- and ref-qualifiers, we kill two birds. With the new common_reference trait and iter_move customization point, we can generalize the Iterator concepts – Readable, IndirectlyMovable, and IndirectCallable in particular – in ways that bring proxy iterators into the fold.

Individually, these are simple changes the committee might want to make anyway. Together, they make a whole new class of data structures usable with the standard algorithms.

The design is presented as a series of diffs to the latest draft of the Ranges Extensions.

2 Implementation Experience

Everything suggested here has been implemented in C++11, where Concepts Lite has been simulated with the help of generalized SFINAE for expressions. In addition, a partial implementation using Concepts exists[2] that works with the “-std=c++1z” support in gcc trunk.

3 Motivation and Scope

The proxy iterator problem has been known since at least 1999 when Herb Sutter wrote his article “When is a container not a container?”[9] about the problems with vector<bool>. Because vector<bool> stores the bools as bits in packed integers rather than as actual bools, its iterators cannot return a real bool& when they are dereferenced; rather, they must return proxy objects that merely behave like bool&. That would be fine except that:

  1. According to the iterator requirements tables, every iterator category stronger than InputIterator is required to return a real reference from its dereference operator (vector is required to have random-access iterators), and
  2. Algorithms that move and swap elements often do not work with proxy references.

Looking forward to a constrained version of the STL, there is one additional problem: the algorithm constraints must accommodate iterators with proxy reference types. This is particularly vexing for the higher-order algorithms that accept functions that are callable with objects of the iterator’s value type.

Why is this an interesting problem to solve? Any data structure whose elements are “virtual” – that don’t physically live in memory – requires proxies to make the data structure readable and (optionally) writable. In addition to vector<bool> and bitset (which currently lacks iterators for no other technical reason), other examples include:

These are all potentially interesting views that, as of today, can only be represented as Input sequences. That severely limits the number of algorithms that can operate on them. The design suggested by this paper would make all of these valid sequences even for random access.

Note that not all iterators that return rvalues are proxy iterators. If the rvalue does not stand in for another object, it is not a proxy. The Palo Alto report[8] lifts the onerous requirement that Forward iterators have true reference types, so it solves the “rvalue iterator” problem. However, as we show below, that is not enough to solve the “proxy iterator” problem.

3.1 Proxy Iterator problems

For all its problems, vector<bool> works surprisingly well in practice, despite the fact that fairly trivial code such as below is not portable.

std::vector<bool> v{true, false, true};
auto i = v.begin();
bool b = false;
using std::swap;
swap(*i, b);      // Not guaranteed to work.

Because of the fact that this code is underspecified, it is impossible to say with certainty which algorithms work with vector<bool>. That fact that many do is due largely to the efforts of implementors and to the fact that bool is a trivial, copyable type that hides many of the nastier problems with proxy references. For more interesting proxy reference types, the problems are impossible to hide.

A more interesting proxy reference type is that of a zip range view from the range-v3[6] library. The zip view adapts two underlying sequences by building pairs of elements on the fly as the zip view is iterated.

vector<int> vi {1,2,3};
vector<string> vs {"a","b","c"};

auto zip = ranges::view::zip(vi, vs);
auto x = *zip.begin();
static_assert(is_same<decltype(x), pair<int&, string&>>{}, "");
assert(&x.first == &vi[0]);
assert(&x.second == &vs[0]);

The zip view’s iterator’s reference type is a prvalue pair object, but the pair holds lvalue references to the elements of the underlying sequences. This proxy reference type exposes more of the fundamental problems with proxies than does vector<bool>, so it will be used in the following discussion.

3.1.1 Permutable proxy iterators

Many algorithms such as partition and sort must permute elements. The Palo Alto report[8] uses a Permutable concept to group the constraints of these algorithms. Permutable is expressed in terms of an IndirectlyMovable concept, which is described as follows:

The IndirectlyMovable and IndirectlyCopyable concepts describe copy and move relationships
between the values of an input iterator, I, and an output iterator Out. For an output iterator
out and an input iterator in, their syntactic requirements expand to:

The iterators into a non-const vector are Permutable. If we zip the two vectors together, is the resulting zip iterator also Permutable? The answer is: maybe, but not with the desired semantics. Given the zip view defined above, consider the following code:

auto i = zip.begin();
auto j = next(i);
*i = move(*j);

Since *j returns a prvalue pair, the move has no effect. The assignment then copies elements instead of moving them. Had one of the underlying sequences been of a move-only type like unique_ptr, the code would fail to compile.

The fundamental problem is that with proxies, the expression move(*j) is moving the proxy, not the element(s) being proxied. Patching this up in the current system would involve returning some special pair-like type from *j and overloading move for it such that it returns a different pair-like type that stores rvalue references. However, move is not a customization point, so the algorithms will not use it. Making move a customization point is one possible fix, but the effects on user code such a change are unknown (and unknowable).

3.1.2 Iterator associated types

The value and reference associated types must be related to each other in a way that can be relied upon by the algorithms. The Palo Alto report defines a Readable concept that expresses this relationship as follows (updated for the new Concepts Lite syntax):

template< class I >
concept bool Readable =
    Semiregular<I> && requires (I i) {
        typename value_type_t<I>;
        { *i } -> const value_type_t<I>&;
    };

The result of the dereference operation must be convertible to a const reference of the iterator’s value type. This works trivially for all iterators whose reference type is an lvalue reference, and it also works for some proxy iterator types. In the case of vector<bool>, the dereference operator returns an object that is implicitly convertible to bool, which can bind to const bool&, so vector<bool>’s iterators are Readable.

But once again we are caught out by move-only types. A zip view that zips together a vector<unique_ptr<int>> and a vector<int> has the following associated types:

Associtated type Value
value_type_t<I> pair<unique_ptr<int>, int>
decltype(*i) pair<unique_ptr<int>&, int&>

To model Readable, the expression “const value_type_t<I>& tmp = *i” must be valid. But trying to initialize a const pair<unique_ptr<int>, int>& with a pair<unique_ptr<int>&, int&> will fail. It ultimately tries to copy from an lvalue unique_ptr. So we see that the zip view’s iterators are not even Readable when one of the element types is move-only. That’s unacceptable.

Although the Palo Alto report lifts the restriction that *i must be an lvalue expression, we can see from the Readable concept that proxy reference types are still not adequately supported.

3.1.3 Constraining higher-order algorithms

The Palo Alto report shows the constrained signature of the for_each algorithm as follows:

template<InputIterator I, Semiregular F>
  requires Function<F, value_type_t<I>>()
F for_each(I first, I last, F f);

Consider calling code

// As before, vi and vs are vectors
auto z = view::zip( vi, vs );
// Let Ref be the zip iterator's reference type:
using Ref = decltype(*z.begin());
// Use for_each to increment all the ints:
for_each( z.begin(), z.end(), [](Ref r) {
    ++r.first;
});

Without the constraint, this code compiles. With it, it doesn’t. The constraint Function<F, value_type_t<I>> checks to see if the lambda is callable with pair<int, string>. The lambda accepts pair<int&, string&>. There is no conversion that makes the call succeed.

Changing the lambda to accept either a “pair<int, string> [const &]” or a “pair<int const &, string const &> [const &]” would make the check succeed, but the body of the lambda would fail to compile or have the wrong semantics. The addition of the constraint has broken valid code, and there is no way to fix it (short of using a generic lambda).

3.2 Problems with the Cross-type Concepts

The purpose of the Common concept in the Palo Alto report is to make cross-type concepts semantically meaningful; it requires that the values of different types, T and U, can be projected into a shared domain where the operation(s) in question can be performed with identical semantics. Concepts like EqualityComparable, TotallyOrdered, and Relation use Common to enforce the semantic coherence that is needed for equational reasoning, even when argument types differ.

However, the syntactic requirements of Common cause these concepts to be overconstrained. The “common” type cannot be any random type with no relation to the other two; rather, objects of the original two types must be explicitly convertible to the common type. The somewhat non-intuitive result of this is that EqualityComparable<T, T> can be false even when EqualityComparable<T> is true. The former has an extra Common<T, T> constraint, which is false for non-movable types: there is no such permissible explicit “conversion” from T to T when T is non-movable.

Although not strictly a problem with proxy iterators, the issues with the foundational concepts effect all the parts of the standard library built upon them and so must be addressed. The design described below offers a simple solution to an otherwise thorny problem.

4 Proposed Design

The design suggested here makes heavier use of an existing API, iter_swap(I, I), promoting it to the status of customization point, thereby giving proxy iterators a way to control how elements are swapped. In addition, it suggests a new customization point: iter_move(I), which can be used for moving an element at a certain position out of sequence, leaving a “hole”. The return type of iter_move is the iterator’s rvalue reference, a new associated type. The IndirectlySwappable and IndirectlyMovable concepts are re-expressed in terms of iter_swap and iter_move, respectively.

The relationships between an iterator’s associated types, currently expressed in terms of convertability, are re-expressed in terms of a shared common reference type. A common reference is much like the familiar common_type trait, except that instead of throwing away top-level cv and ref qualifiers, they are preserved. Informally, the common reference of two reference types is the minimally-qualified reference type to which both types can bind. Like common_type, the new common_reference trait can be specialized.

4.1 Impact on the Standard

4.1.1 Overview, for implementers

The algorithms must be specified to use iter_swap and iter_move when swapping and moving elements. The concepts must be respecified in terms of the new customization points, and a new type trait, common_reference, must be specified and implemented. The known shortcomings of common_type (e.g., difficulty of specialization) must be addressed. (The formulation of common_type given in this paper fixes all known issues.) Care must be taken in the algorithm implementations to hew to the valid expressions for the iterator concepts. The algorithm constraints must be respecified to accommodate proxy iterators.

4.1.2 Overview, for users

For user code, the changes are minimal. Little to no conforming code that works today will stop working after adoping this resolution. The changes to common_type are potentially breaking, but only for conversion sequences that are sensitive to cv qualification and value category, and the committee has shown no reluctance to make similar changes to common_type before. The addition of common_reference gives recourse to users who care about it.

When adapting generic code to work with proxy iterators, calls to swap and move should be replaced with iter_swap and iter_move, and for calls to higher-order algorithms, generic lambdas are the preferred solution. When that’s not possible, functions can be changed to take arguments by the iterator’s common reference type, which is the result of applying the common_reference trait to reference_t<I> and value_type_t<I>&. (An iter_common_reference_t<I> type alias is suggested to make this simpler.)

4.1.3 CommonReference and Common

The suggested common_reference type trait and the CommonReference concept that uses it, which is used to express the constraints between an iterator’s associated types, takes two (possibly cv- and ref-qualified) types and finds a common type (also possibly qualified) to which they can both be converted or bound. When passed two reference types, common_reference tries to find another reference type to which both references can bind. (common_reference may return a non-reference type if no such reference type is found.) If common references exist between an iterator’s associated types, then generic code knows how to manipulate values read from the iterator, and the iterator “makes sense”.

Like common_type, common_reference may also be specialized on user-defined types, and this is the hook that is needed to make proxy references work in a generic context. As a purely practical matter, specializing such a template presents some issues. Would a user need to specialize common_reference for every permutation of cv- and ref-qualifiers, for both the left and right arguments? Obviously, such an interface would be broken. The issue is that there is no way in C++ to partially specialize on type qualifiers.

Rather, common_reference is implemented in terms of another template: basic_common_reference. The interface to basic_common_reference is given below:

template <class T, class U,
  template <class> class TQual,
  template <class> class UQual>
struct basic_common_reference;

An instantiation of common_reference<T cv &, U cv &> defers to basic_common_reference<T, U, tqual, uqual>, where tqual is a unary alias template such that tqual<T> is T cv &. Basically, the template template parameters encode the type qualifiers that got stripped from the first two arguments. That permits users to effectively partially specialize basic_common_reference – and hence common_reference – on type qualifiers.

For instance, here is the partial specialization that find the common “reference” of two tuples of references – which is a proxy reference.

template <class...Ts, class...Us,
  template <class> class TQual, 
  template <class> class UQual>
  requires sizeof...(Ts) == sizeof...(Us) &&
    (CommonReference<TQual<Ts>, UQual<Us>>() &&...)
struct basic_common_reference<tuple<Ts...>, tuple<Us...>, TQual, UQual> {
  using type = tuple<
    common_reference_t<TQual<Ts>, UQual<Us>>...>;    
};

With this specialization, the common reference between the types tuple<int, double>& and tuple<const int&, double&> is computed as tuple<const int&, double&>. (The fact that there is currently no conversion from an lvalue of type tuple<int, double> to tuple<const int&, double&> means that these two types do not model CommonReference. Arguably, such a conversion should exist.)

A reference implementation of common_type and common_reference can be found in Appendix 1.

4.1.3.1 CommonReference and the Cross-Type Concepts

The CommonReference concept also eliminates the problems with the cross-type concepts as described in the section “Problems with the Cross-type Concepts”. By using the CommonReference concept instead of Common in concepts like EqualityComparable and TotallyOrdered, these concepts are no longer overconstrained since a const lvalue of type “T” can bind to the common reference type “const T&”, regardless of whether T is movable or not. CommonReference, like Common, ensures that there is a shared domain in which the operation(s) in question are semantically meaningful, so equational reasoning is preserved.

4.1.4 Permutable: iter_swap and iter_move

Today, iter_swap is a useless vestige. By expanding its role, we can press it into service to solve the proxy iterator problem, at least in part. The primary std::swap and std::iter_swap functions get constrained as follows:

// swap is defined in <utility>
template <Movable T>
void swap(T &t, T &u) noexcept(/*...*/) {
  T tmp = move(t);
  t = move(u);
  u = move(tmp);
}

// Define iter_swap in terms of swap if that's possible
template <Readable I1, Readable I2>
  // Swappable concept defined in new <concepts> header
  requires Swappable<reference_t<I1>, reference_t<I2>>()
void iter_swap(I1 r1, I2 r2) noexcept(noexcept(swap(*r1, *r2))) {
  swap(*r1, *r2);
}

By making iter_swap a customization point and requiring all algorithms to use it instead of swap, we make it possible for proxy iterators to customize how elements are swapped.

Code that currently uses “using std::swap; swap(*i1, *i2);” can be trivially upgraded to this new formulation by doing “using std::iter_swap; iter_swap(i1, i2)” instead.

In addition, this paper recommends adding a new customization point: iter_move. This is for use by those permuting algorithms that must move elements out of sequence temporarily. iter_move is defined essentially as follows:

template <class I>
using __iter_move_t =
  conditional_t<
    is_reference<reference_t<I>>::value,
    remove_reference_t<reference_t<I>> &&,
    decay_t<reference_t<I>>;

template <class I>
__iter_move_t<I> iter_move(I r)
  noexcept(noexcept(__iter_move_t<I>(std::move(*r)))) {
  return std::move(*r);
}

Code that currently looks like this:

value_type_t<I> tmp = std::move(*it);
// ...
*it = std::move(tmp);

can be upgraded to use iter_move as follows:

using std::iter_move;
value_type_t<I> tmp = iter_move(it);
// ...
*it = std::move(tmp);

With iter_move, the Readable concept picks up an additional associated type: the return type of iter_move, which we call rvalue_reference_t.

template <class I>
using rvalue_reference_t = decltype(iter_move(declval<I&>()));

This type gets used in the definition of the new iterator concepts described below.

With the existence of iter_move, it makes it possible to implement iter_swap in terms of iter_move, just as the default swap is implemented in terms of move. But to take advantage of all the existing overloads of swap, we only want to do that for types that are not already Swappable.

template <Readable I1, Readable I2>
  requires !Swappable<reference_t<I1>, reference_t<I2>>() &&
    IndirectlyMovable<I1, I2>() && IndirectlyMovable<I2, I1>()
void iter_swap(I1 r1, I2 r2)
  noexcept(is_nothrow_indirectly_movable<I1, I2>::value &&
           is_nothrow_indirectly_movable<I2, I1>::value) {
  value_type_t<I1> tmp = iter_move(r1);
  *r1 = iter_move(r2);
  *r2 = std::move(tmp);
}

See below for the updated IndirectlyMovable concept.

4.1.5 Iterator Concepts

Rather than requiring that an iterator’s reference_t be convertible to const value_type_t<I>&– which is overconstraining for proxied sequences – we require that there is a shared reference-like type to which both references and values can bind. The new rvalue_reference_t associated type needs a similar constraint.

Only the syntactic requirements are given here. The semantic requirements are described in the Technical Specifications section.

4.1.5.1 Concept Readable

Below is the suggested new formulation for the Readable concept:

template <class I>
concept bool Readable() {
  return Movable<I>() && DefaultConstructible<I>() &&
    requires (const I& i) {
      // Associated types
      typename value_type_t<I>;
      typename reference_t<I>;
      typename rvalue_reference_t<I>;

      // Valid expressions
      { *i } -> Same<reference_t<I>>;
      { iter_move(i) } -> Same<rvalue_reference_t<I>>;
    } &&
    // Relationships between associated types
    CommonReference<reference_t<I>, value_type_t<I>&>() &&
    CommonReference<reference_t<I>, rvalue_reference_t<I>>() &&
    CommonReference<rvalue_reference_t<I>, const value_type_t<I>&>() &&
    // Extra sanity checks (not strictly needed)
    Same<
      std::common_reference_t<reference_t<I>, value_type_t<I>>,
      value_type_t<I>>() &&
    Same<
      std::common_reference_t<rvalue_reference_t<I>, value_type_t<I>>,
      value_type_t<I>>();
}

// A generally useful dependent type
template <Readable I>
using iter_common_reference_t =
  common_reference_t<reference_t<I>, value_type_t<I>&>;

4.1.5.2 Concepts IndirectlyMovable and IndirectlyCopyable

Often we want to move elements indirectly, from one type that is readable to another that is writable. IndirectlyMovable groups the necessary requirements. We can derive those requirements by looking at the implementation of iter_swap above that uses iter_move. They are:

  1. value_type_t<In> value = iter_move(in)
  2. value = iter_move(in) // by extension
  3. *out = iter_move(in)
  4. *out = std::move(value)

We can formalize this as follows:

template <class In, class Out>
concept bool IndirectlyMovable() {
  return Readable<In>() && Movable<value_type_t<In>>() &&
    Constructible<value_type_t<In>, rvalue_reference_t<In>>() &&
    Assignable<value_type_t<I>&, rvalue_reference_t<In>>() &&
    MoveWritable<Out, rvalue_reference_t<In>>() &&
    MoveWritable<Out, value_type_t<I>>();
}

Although more strict than the Palo Alto formulation, which only requires *out = move(*in), this concept gives algorithm implementors greater license for storing intermediates when moving elements indirectly, a capability required by many of the permuting algorithms.

The IndirectlyCopyable concept is defined similarly:

template <class In, class Out>
concept bool IndirectlyCopyable() {
  return IndirectlyMovable<In, Out>() &&
    Copyable<value_type_t<In>>() &&
    Constructible<value_type_t<In>, reference_t<In>>() &&
    Assignable<value_type_t<I>&, reference_t<In>>() &&
    Writable<Out, reference_t<In>>() &&
    Writable<Out, value_type_t<I>>();
}

4.1.5.3 Concept IndirectlySwappable

With overloads of iter_swap that work for Swappable types and IndirectlyMovable types, the IndirectlySwappable concept is trivially implemented in terms of iter_swap, with extra checks to test for symmetry:

template <class I1, class I2>
concept bool IndirectlySwappable() {
  return Readable<I1>() && Readable<I2>() &&
    requires (const I1 i1, const I2 i2) {
      iter_swap(i1, i2);
      iter_swap(i2, i1);
      iter_swap(i1, i1);
      iter_swap(i2, i2);
    };
}

4.1.6 Algorithm constraints: IndirectCallable

Further problems with proxy iterators arise while trying to constrain algorithms that accept callback functions from users: predicates, relations, and projections. Below, for example, is part of the implementation of unique_copy from the SGI STL[7].

_Tp value = *first;
*result = value;
while (++first != last)
  if (!binary_pred(value, *first)) {
    value = *first;
    *++result = value;
  }

The expression “binary_pred(value, *first)” is invoking binary_pred with an lvalue of the iterator’s value type and its reference type. If first is a vector<bool> iterator, that means binary_pred must be callable with bool& and vector<bool>::reference. All over the STL, predicates are called with every permutation of value_type_t<I>& and reference_t<I>.

The Palo Alto report uses the simple Predicate<F, value_type_t<I>, value_type_t<I>> constraint on such higher-order algorithms. When an iterator’s operator* returns an lvalue reference or a non-proxy rvalue, this simple formulation is adequate. The predicate F can simply take its arguments by “const value_type_t<I>&”, and everything works.

With proxy iterators, the story is more complicated. As described in the section Constraining higher-order algorithms, the simple constraint formulation of the Palo Alto report either rejects valid uses, forces the user to write inefficient code, or leads to compile errors.

Since the algorithm may choose to call users’ functions with every permutation of value type and reference type arguments, the requirements must state that they are all required. Below is the list of constraints that must replace a constraint such as Predicate<F, value_type_t<I>, value_type_t<I>>:

There is no need to require that the predicate is callable with the iterator’s rvalue reference type. The result of iter_move in an algorithm is always used to initialize a local variable of the iterator’s value type. In addition, we can add one more requirement to give the algorithms the added flexibility of using monomorphic functions internal to their implementation:

Rather than require that this unwieldy list appear in the signature of every algorithm, we can bundle them up into the IndirectPredicate concept, shown below:

template <class F, class I1, class I2>
concept bool IndirectPredicate() {
  return Readable<I1>() && Readable<I2>() &&
    Predicate<F, value_type_t<I1>, value_type_t<I2>>() &&
    Predicate<F, value_type_t<I1>, reference_t<I2>>() &&
    Predicate<F, reference_t<I1>, value_type_t<I2>>() &&
    Predicate<F, reference_t<I1>, reference_t<I2>>() &&
    Predicate<F, iter_common_reference_t<I1>, iter_common_reference_t<I2>>();
}

The algorithm’s constraints in the latest Ranges TS draft are already expressed in terms of a simpler set of Indirect callable concepts, so this change would mostly be localized to the concept definitions.

From the point of view of the users who must author predicates that satisfy these extra constraints, no changes are needed for any iterator that is valid today; the added constraints are satisfied automatically for non-proxy iterators. When authoring a predicate to be used in conjunction with proxy iterators, the simplest solution is to use a polymorphic lambda for the predicate. For instance:

// Polymorphic lambdas will work with proxy iterators:
sort(first, last, [](auto&& x, auto&& y) {return x < y;});

If using a polymorphic lambda is undesirable, an alternate solution is to use the iterator’s common reference type:

// Use the iterator's common reference type to define a monomorphic relation:
using X = iter_common_reference_t<I>;
sort(first, last, [](X&& x, X&& y) {return x < y;});

5 Alternate Designs

5.1 Make move a customization point

Rather than adding a new customization point (iter_move) we could press std::move into service as a partial solution to the proxy iterator problem. The idea would be to make expressions like std::move(*it) do the right thing for iterators like the zip iterator described above. That would involve making move a customization point and letting it return something other than an rvalue reference type, so that proxy lvalues can be turned into proxy rvalues. (This solution would still require common_reference to solve the other problems described above.)

At first blush, this solution makes a kind of sense: swap is a customization point, so why isn’t move? That logic overlooks the fact that users already have a way to specify how types are moved: with the move constructor and move assignment operator. Rvalue references and move semantics are complicated enough without adding yet another wrinkle, and overloads of move that return something other than T&& qualify as a wrinkle; move is widely used, and there’s no telling how much code could break if move returned something other than a T&&.

It’s also a breaking change since move is often called qualified, as std::move. That would not find any overloads in other namespaces unless the approach described in N4381[5] were adopted. Turning move into a namespace-scoped function object (the customization point design suggested by N4381) comes with its own risks, as described in that paper.

Making move the customization point instead of iter_move reduces design flexibility for authors of proxy iterator types since argument-dependent dispatch happens on the type of the proxy reference instead of the iterator. Consider the zip iterator described above, whose reference type is a prvalue pair of lvalue references. To make this iterator work using move as the customization point would require overloading move on pair. That would be a breaking change since move of pair already has a meaning. Rather, the zip iterator’s reference type would have to be some special proxy_pair type just so that move could be overloaded for it. That’s undesirable.

A correlary of the above point involves proxy-like types like reference_wrapper. Given an lvalue reference_wrapper named “x”, it’s unclear what move(x) should do. Should it return an rvalue reference to “x”, or should it return a temporary object that wraps an rvalue reference to “x.get()”? With move as a customization point, there is often not enough context to say with certainty what behavior to assign to move for a type that stores references.

For all these reasons, this paper prefers to add a new, dedicated API – iter_move – whose use is unambiguous.

5.2 New iterator concepts

In N1640[1], Abrahams et.al. describe a decomposition of the standard iterator concept hierarchy into access concepts: Readable, Writable, Swappable, and Lvalue; and traversal concepts: SinglePass, Forward, Bidirectional, and RandomAccess. Like the design suggested in this paper, the Swappable concept from N1640 is specified in terms of iter_swap. Since N1640 was written before move semantics, it does not have anything like iter_move, but it’s reasonable to assume that it would have invented something similar.

Like the Palo Alto report, the Readable concept from N1640 requires a convertibility constraint between an iterator’s reference and value associated types. As a result, N1640 does not adequately address the proxy reference problem as presented in this paper. In particular, it is incapable of correctly expressing the relationship between a move-only value type and its proxy reference type. Also, the somewhat complicated iterator tag composition suggested by N1640 is not necessary in a world with concept-based overloading.

In other respects, N1640 agrees with the STL design suggested by the Palo Alto report and the Ranges TS, which also has concepts for Readable and Writable. In the Palo Alto design, these “access” concepts are not purely orthogonal to the “traversal” concepts of InputIterator, ForwardIterator, however, since the latter are not pure traversal concepts; rather, these iterators are all Readable. The standard algorithms have little need for writable-but-not-readable random access iterators, for instance, so a purely orthogonal design does not accurately capture the requirements clusters that appear in the algorithm constraints. The binary concepts IndirectlyMovable<I, O>, IndirectlyCopyable<I, O>, and IndirectlySwappable<I1, I2> from the Palo Alto report do a better job of grouping common requirements and reducing verbosity in the algorithm constraints.

5.3 Cursor/Property Map

N1873[3], the “Cursor/Property Map Abstraction”, suggests a radical solution to the proxy iterator problem, among others: break iterators into two distint entities – a cursor that denotes position and a property map that facilitates element access. A so-called property map (pm) is a polymorphic function that is used together with a cursor (c) to read an element (pm(*c)) and with a cursor and value (v) to write an element (pm(*c, v)). This alternate syntax for element access obviates the need for proxy objects entirely, so the proxy iterator problem simply disappears.

The problems with this approach are mostly practical: the model is more complicated and the migration story is poor. No longer is a single object sufficient for both traversal and access. Three arguments are needed to denote a range: a begin cursor, an end cursor, and a property map. Generic code must be updated to account for the new syntax and for the extra property map argument. In other words, this solution is more invasive than the one this document presents.

5.4 Language support

In private exchange, Sean Parent suggested a more radical fix for the proxy reference problem: change the language. With his suggestion, it would be possible to specify that a type is a proxy reference with a syntax such as:

struct bool_reference : bool& {
    // ...
}

Notice the “inheritance” from bool&. When doing template type deduction, a bool_reference can bind to a T&, with T deduced to bool. This solution has not been explored in depth. It is unclear how to control which operations are to be performed on the proxy itself and which on the object being proxied, or under which circumstances, if any, that is desirable. The impact of changing template type deduction and possibly overload resolution to natively support proxy references is unknown.

6 Technical Specifications

This section is written as a set of diffs against N4382, “C++ Extensions for Ranges” and N4141 (C++14), except where otherwise noted.

6.0.1 Chapter 19: Concepts

To [19.2] Core Language Concepts, add the following:

19.2.X Concept CommonReference [concepts.lib.corelang.commonref]

1. If std::common_reference_t<T, U> is well-formed and denotes a type C such that both ConvertibleTo<T, C>() and ConvertibleTo<U, C>() are satisfied, then T and U share a common reference type, C. [ Note: C could be the same as T, or U, or it could be a different type. C may be a reference type. C need not be unique. –end note ]

template <class T, class U>
concept bool CommonReference() {
  return
    requires (T (&t)(), U (&u)()) {
      typename std::common_reference_t<T, U>;
      typename std::common_reference_t<U, T>;
      requires Same<std::common_reference_t<T, U>,
                    std::common_reference_t<U, T>>();
      std::common_reference_t<T, U>(t());
      std::common_reference_t<T, U>(u());
    };
}

2. Let C be std::common_reference_t<T, U>. Let t be a function whose return type is T, and let u be a function whose return type is U. CommonReference<T, U>() is satisfied if and only if

(2.1) – C(t()) equals C(t()) if and only if t is a regular function ([19.1.1] REF(concepts.lib.general.equality)).
(2.2) – C(u()) equals C(u()) if and only if u is a regular function ([19.1.1] REF(concepts.lib.general.equality)).

3. [ Note: Users are free to specialize common_reference and basic_common_reference when at least one parameter depends on a user-defined type. Those specializations are considered by the CommonReference concept. –end note ]

Change 19.2.5 Concept Common to the following:

template <class T, class U>
concept bool Common() {
  return CommonReference<const T&, const U&>() &&
    requires (T (&t)(), U (&u)()) {
      typename std::common_type_t<T, U>;
      typename std::common_type_t<U, T>;
      requires Same<std::common_type_t<T, U>,
                    std::common_type_t<U, T>>();
      std::common_type_t<T, U>(t());
      std::common_type_t<T, U>(u());
      requires CommonReference<add_lvalue_reference_t<common_type_t<T, U>>,
                               common_reference_t<add_lvalue_reference_t<const T>,
                                                  add_lvalue_reference_t<const U>>>();
    };
}

2. Let C be common_type_t<T, U>. Let t be a function whose return type is T, and let u be a function whose return type is U. Common<T, U>() is satisfied if and only if

(2.1) – C(t()) equals C(t()) if and only if t is a regular function ([19.1.1] REF(concepts.lib.general.equality)).
(2.2) – C(u()) equals C(u()) if and only if u is a regular function ([19.1.1] REF(concepts.lib.general.equality)).

3. [ Note: Users are free to specialize common_type when at least one parameter depends on a user-defined type. Those specializations are considered by the Common concept. –end note ]

Change the definitions of the cross-type concepts Swappable<T, U> ([concepts.lib.corelang.swappable]), EqualityComparable<T, U> ([concepts.lib.compare.equalitycomparable]), TotallyOrdered<T, U> ([concepts.lib.compare.totallyordered]), and Relation<F, T, U> ([concepts.lib.functions.relation]) to use CommonReference<const T&, const U&> instead of Common<T, U>.

In addition, Relation<F, T, U> requires Relation<F, std::common_reference_t<const T&, const U&>> rather than Relation<F, std::common_type_t<T, U>>.

6.0.2 Chapter 20: General utilities

Change 20.10.2 [meta.type.synop], header <type_traits> synopsis, as indicated (N.B., in namespace std):

[Editorial note:is_[nothrow_]swappable[_with] traits taken from N4511: Adding [nothrow-]swappable traits. –end note]

namespace std {
  […]
  // 20.10.4.3, type properties:
  […]
  template <class T> struct is_move_assignable;
  
  template <class T, class U> struct is_swappable_with;
  template <class T> struct is_swappable;
  
  template <class T> struct is_destructible;
  […]
  template <class T> struct is_nothrow_move_assignable;

  template <class T, class U> struct is_nothrow_swappable_with;
  template <class T> struct is_nothrow_swappable;
  
  template <class T> struct is_nothrow_destructible;
  […]

  // 20.10.7.6, other transformations:
  […]
  template <class... T> struct common_reference;
  template <class T, class U, template <class> class TQual, template <class> class UQual>
    struct basic_common_reference { };
  template <class... T> struct common_reference;
  template <class T> struct underlying_type;
  […]
  template <class... T>
    using common_type_t = typename common_type<T...>::type;
  template <class... T>
    using common_reference_t = typename common_reference<T...>::type;
  template <class T>
    using underlying_type_t = typename underlying_type<T>::type;
  […]
}

Change 20.10.4.3 [meta.unary.prop], Table 49 — “Type property predicates”, as indicated:

[Editorial note: – The following is taken from N4511: Adding [nothrow-]swappable traits. –end note]

Table 49 — Type property predicates
Template Condition Preconditions
template <class T, class U>
struct is_swappable_with;
The expressions swap(declval<T>(), declval<U>()) and
swap(declval<U>(), declval<T>()) are each well-formed
when treated as an unevaluated operand (Clause 5) in an overload-resolution
context for swappable values (17.6.3.2 [swappable.requirements]). Access
checking is performed as if in a context unrelated to T and U. Only the
validity of the immediate context of the swap expressions is considered.
[Note: The compilation of the expressions can result in side effects such
as the instantiation of class template specializations and function template
specializations, the generation of implicitly-defined functions, and so on. Such
side effects are not in the "immediate context" and can result in the program
being ill-formed. — end note]
T and U shall be complete types,
(possibly cv-qualified) void, or
arrays of unknown bound.
template <class T>
struct is_swappable;
For a referenceable type T, the same result
as is_swappable_with<T&, T&>::value,
otherwise false.
T shall be a complete type,
(possibly cv-qualified) void, or an
array of unknown bound.
template <class T, class U>
struct is_nothrow_swappable_with;
is_swappable_with<T, U>::value is true
and each swap expression of the definition of
is_swappable_with<T, U> is known not to throw
any exceptions (5.3.7 [expr.unary.noexcept]).
T and U shall be complete types,
(possibly cv-qualified) void, or
arrays of unknown bound.
template <class T>
struct is_nothrow_swappable;
For a referenceable type T, the same result
as is_nothrow_swappable_with<T&, T&>::value,
otherwise false.
T shall be a complete type,
(possibly cv-qualified) void, or an
array of unknown bound.

Change Table 57 Other Transformations as follows:

Table 49 — Type property predicates
Template Condition Comments
template
struct common_type;
The member typedef type shall be defined or omitted as specified below.
If it is omitted, there shall be no member type. All typesEach type in
the parameter pack T shall be complete or (possibly cv) void. A program
may specialize this trait if at least one template parameter in the
specialization isdepends on a user-defined type and sizeof...(T) == 2.
[ Note: Such specializations are needed only when explicit conversions
are desired among the template arguments. —end note ]
template <class T, class U,
  template <class> class TQual,
  template <class> class UQual>
struct basic_common_reference;
The primary template shall have no member typedef type. A program
may specialize this trait if at least one template parameter in the specialization
depends on a user-defined type. In such a specialization, a member typedef
type may be defined or omitted. If it is omitted, there shall be no member
type. [ Note: Such specializations may be used to influence the result
of common_referenceend note ]
template <class... T>
struct common_reference;
The member typedef type shall be defined or omitted as specified below. If
it is omitted, there shall be no member type. Each type in the parameter pack T
shall be complete or (possibly cv) void. A program may specialize this
trait if at least one template parameter in the specialization depends on a
user-defined type and sizeof...(T) == 2. [ Note: Such specializations are
needed to properly handle proxy reference types in generic code. —end note ]

Delete [meta.trans.other]/p3 and replace it with the following:

3. Let CREF(A) be add_lvalue_reference_t<const remove_reference_t<A>>. Let UNCVREF(A) be remove_cv_t<remove_reference_t<A>>. Let XREF(A) denote a unary template T such that T<UNCVREF(A)> denotes the same type as A. Let COPYCV(FROM, TO) be an alias for type TO with the addition of FROM’s top-level cv-qualifiers. [Example:COPYCV(int const, short volatile) is an alias for short const volatile. – end example] Let RREF_RES(Z) be remove_reference_t<Z>&& if Z is a reference type or Z otherwise. Let COND_RES(X, Y) be decltype(declval<bool>() ? declval<X>() : declval<Y>()). Given types A and B, let X be remove_reference_t<A>, let Y be remove_reference_t<B>, and let COMMON_REF(A, B) be:

(3.1) – If A and B are both lvalue reference types, COMMON_REF(A, B) is COND_RES(COPYCV(X, Y) &, COPYCV(Y, X) &).
(3.2) – Otherwise, let C be RREF_RES(COMMON_REF(X&, Y&)). If A and B are both rvalue reference types, and C is well-formed, and is_convertible<A, C>::value and is_convertible<B, C>::value are true, then COMMON_REF(A, B) is C.
(3.3) – Otherwise, let D be COMMON_REF(const X&, Y&). If A is an rvalue reference and B is an lvalue reference and D is well-formed and is_convertible<A, D>::value is true, then COMMON_REF(A, B) is D.
(3.4) – Otherwise, if A is an lvalue reference and B is an rvalue reference, then COMMON_REF(A, B) is COMMON_REF(B, A).
(3.5) – Otherwise, COMMON_REF(A, B) is decay_t<COND_RES(CREF(A), CREF(B))>.

If any of the types computed above are ill-formed, then COMMON_REF(A, B) is ill-formed.

4. [Editorial note: – The following text in black is taken from the current C++17 draft –end note] For the common_type trait applied to a parameter pack T of types, the member type shall be either defined or not present as follows:

(4.1) – If sizeof...(T) is zero, there shall be no member type.
(4.2) – Otherwise, if sizeof...(T) is one, let T0T1 denote the sole type in the pack T. The member typedef type shall denote the same type as decay_t<T0T1>.
(4.3) – Otherwise, if sizeof...(T) is two, let T1 and T2 denote the two types in the pack T, and let D1 and D2 be decay_t<T1> and decay_t<T2> respectively. Then

(4.3.1) – If D1 and T1 denote the same type and D2 and T2 denote the same type, then

(4.3.1.1) – If COMMON_REF(T1, T2) is well-formed, then the member typedef type denotes that type.
(4.3.1.2) – Otherwise, there shall be no member type.

(4.3.2) – Otherwise, if common_type_t<D1, D2> is well-formed, then the member typedef type denotes that type.
(4.3.3) – Otherwise, there shall be no member type.

(4.4) – Otherwise, if sizeof...(T) is greater than onetwo, let T1, T2, and Rest, respectively, denote the first, second, and (pack of) remaining types comprising T. [ Note: sizeof...(R) may be zero. –end note ] Let C denote the type, if any, of an unevaluated conditional expression (5.16) whose first operand is an arbitrary value of type bool, whose second operand is an xvalue of type T1, and whose third operand is an xvalue of type T2.be the type common_type_t<T1, T2>. Then

(4.4.1) – If there is such a type C, the member typedef type shall denote the same type, if any, as common_type_t<C, Rest...>.
(4.4.2) – Otherwise, there shall be no member type.

5. For the common_reference trait applied to a parameter pack T of types, the member type shall be either defined or not present as follows:

(5.1) – If sizeof...(T) is zero, there shall be no member type.
(5.2) – Otherwise, if sizeof...(T) is one, let T1 denote the sole type in the pack T. The member typedef type shall denote the same type as T1.
(5.3) – Otherwise, if sizeof...(T) is two, let T1 and T2 denote the two types in the pack T. Then

(5.3.1) – If COMMON_REF(T1, T2) denotes a valid reference type then the member typedef type denotes that type.
(5.3.2) – Otherwise, if basic_common_reference<UNCVREF(T1), UNCVREF(T2), XREF(T1), XREF(T2)>::type is well-formed, then the member typedef type denotes that type.
(5.3.3) – Otherwise, if common_type_t<T1, T2> is well-formed, then the member typedef type denotes that type.
(5.3.4) – Otherwise, there shall be no member type.

(5.4) – Otherwise, if sizeof...(T) is greater than two, let T1, T2, and Rest, respectively, denote the first, second, and (pack of) remaining types comprising T. Let C be the type common_reference_t<T1, T2>. Then

(5.4.1) – If there is such a type C, the member typedef type shall denote the same type, if any, as common_reference_t<C, Rest...>.
(5.4.2) – Otherwise, there shall be no member type.

6.0.3 Chapter 24. Iterators

Change concept Readable ([readable.iterators]) as follows:

template <class I>
concept bool Readable() {
  return Movable<I>() && DefaultConstructible<I>() &&
    requires (const I& i) {
      typename value_type_t<I>;
      typename reference_t<I>;
      typename rvalue_reference_t<I>;
      { *i } -> Same<reference_t<I>>;
      { iter_move(i) } -> Same<rvalue_reference_t<I>>;
    } &&
    // Relationships between associated types
    CommonReference<reference_t<I>, value_type_t<I>&>() &&
    CommonReference<reference_t<I>, rvalue_reference_t<I>>() &&
    CommonReference<rvalue_reference_t<I>, const value_type_t<I>&>() &&
    Same<
      std::common_reference_t<reference_t<I>, value_type_t<I>>,
      value_type_t<I>>() &&
    Same<
      std::common_reference_t<rvalue_reference_t<I>, value_type_t<I>>,
      value_type_t<I>>();
}

Add a new paragraph (2) to the description of Readable:

2. Overload resolution ([over.match]) on the expression
iter_move(i) selects a unary non-member function
iter_move” from a candidate set that includes the
iter_move function found in
<experimental/ranges/iterator> ([iterator.synopsis])
and the lookup set produced by argument-dependent lookup
([basic.lookup.argdep]).

Change concept IndirectlyMovable ([indirectlymovable.iterators]) to be as follows:

template <class In, class Out>
concept bool IndirectlyMovable() {
  return Readable<In>() && Movable<value_type_t<In>>() &&
    Constructible<value_type_t<In>, rvalue_reference_t<In>>() &&
    Assignable<value_type_t<In>&, rvalue_reference_t<In>>() &&
    MoveWritable<Out, rvalue_reference_t<In>>() &&
    MoveWritable<Out, value_type_t<In>>();
}

Change the description of the IndirectlyMovable concept ([indirectlymovable.iterators]), to be:

2. Let i be an object of type In, let o be a dereferenceable
object of type Out, and let v be an object of type
value_type_t<In>. Then IndirectlyMovable<In, Out>() is satisfied
if and only if
(2.1) – The expression value_type_t<In>(iter_move(i)) has a value
that is equal to the value *i had before the expression was
evaluated.
(2.2) – After the assignment v = iter_move(i), v is equal
to the value of *i before the assignment.
(2.3) – If Out is Readable, after the assignment
*o = iter_move(i), *o is equal to the value of *i before
the assignment.
(2.4) – If Out is Readable, after the assignment
*o = std::move(v), *o is equal to the value of *i before
the assignment.

Change concept IndirectlyCopyable ([indirectlycopyable.iterators]) to be as follows:

template <class In, class Out>
concept bool IndirectlyCopyable() {
  return IndirectlyMovable<In, Out>() && Copyable<value_type_t<In>>() &&
    Constructible<value_type_t<In>, reference_t<In>>() &&
    Assignable<value_type_t<In>&, reference_t<In>>() &&
    Writable<Out, reference_t<In>>() &&
    Writable<Out, value_type_t<In>>();
}

Change the description of the IndirectlyCopyable concept ([indirectlycopyable.iterators]), to be:

2. Let i be an object of type In, let o be a dereferenceable
object of type Out, and let v be a const object of type
value_type_t<In>. Then IndirectlyCopyable<In, Out>() is satisfied
if and only if
(2.1) – The expression value_type_t<In>(*i) has a value
that is equal to the value of *i.
(2.2) – After the assignment v = *i, v is equal
to the value of *i.
(2.3) – If Out is Readable, after the assignment
*o = *i, *o is equal to the value of *i.
(2.4) – If Out is Readable, after the assignment
*o = v, *o is equal to the value of v.

Change concept IndirectlySwappable ([indirectlyswappable.iterators]) to be as follows:

template <class I1, class I2 = I1>
concept bool IndirectlySwappable() {
  return Readable<I1>() && Readable<I2>() &&
    requires (const I1 i1, const I2 i2) {
      iter_swap(i1, i2);
      iter_swap(i2, i1);
      iter_swap(i1, i1);
      iter_swap(i2, i2);
    };
}

Change the description of IndirectlySwappable:

1. Overload resolution ([over.match]) on each of the four
iter_swap expressions selects a binary non-member function
iter_swap” from a candidate set that includes the two
iter_swap functions found in
<experimental/ranges/iterator> ([iterator.synopsis])
and the lookup set produced by argument-dependent lookup
([basic.lookup.argdep]).

2. Given an object i1 of type I1 and an object i2 of
type I2, IndirectlySwappable<I1, I2>() is satisfied if after
iter_swap(i1, i2), the value of *i1 is equal to the value of
*i2 before the call, and vice versa.

Swap subsections 24.3.3 ([projected.indirectcallables]) and 24.3.4 ([indirectfunct.indirectcallables]), and change the definition of the projected struct ([projected.indirectcallables]) to the following:

template <Readable I, IndirectRegularCallable<I> Proj>
struct projected {
  using value_type = decay_t<result_of_t<as_function_t<Proj&>(value_type_t<I>)>>;
  auto operator*() const ->
    result_of_t<as_function_t<Proj&>(reference_t<I>)>;
};

template <WeaklyIncrementable I, class Proj>
struct difference_type<projected<I, Proj>> {
  using type = difference_type_t<I>;
};

Change 24.3.4 “Indirect callables” ([indirectfunc.indirectcallables]) as described as follows: Change IndirectCallable, IndirectRegularCallable, IndirectCallablePredicate, IndirectCallableRelation, and IndirectCallableStrictWeakOrder as follows:

template <class F>
concept bool IndirectCallable() {
  return Function<as_function_t<F>>();
}
template <class F, class I>
concept bool IndirectCallable() {
  return Readable<I>() &&
    Function<as_function_t<F>, value_type_t<I>>() &&
    Function<as_function_t<F>, reference_t<I>>() &&
    Function<as_function_t<F>, iter_common_reference_t<I>>();
}
template <class F, class I1, class I2>
concept bool IndirectCallable() {
  return Readable<I1>() && Readable<I2>() &&
    Function<as_function_t<F>, value_type_t<I1>, value_type_t<I2>>() &&
    Function<as_function_t<F>, value_type_t<I1>, reference_t<I2>>() &&
    Function<as_function_t<F>, reference_t<I1>, value_type_t<I2>>() &&
    Function<as_function_t<F>, reference_t<I1>, reference_t<I2>>() &&
    Function<as_function_t<F>, iter_common_reference_t<I1>, iter_common_reference_t<I2>>();
}

template <class F>
concept bool IndirectRegularCallable() {
  return RegularFunction<as_function_t<F>>();
}
template <class F, class I>
concept bool IndirectRegularCallable() {
  return Readable<I>() &&
    RegularFunction<as_function_t<F>, value_type_t<I>>() &&
    RegularFunction<as_function_t<F>, reference_t<I>>() &&
    RegularFunction<as_function_t<F>, iter_common_reference_t<I>>();
}
template <class F, class I1, class I2>
concept bool IndirectRegularCallable() {
  return Readable<I1>() && Readable<I2>() &&
    RegularFunction<as_function_t<F>, value_type_t<I1>, value_type_t<I2>>() &&
    RegularFunction<as_function_t<F>, value_type_t<I1>, reference_t<I2>>() &&
    RegularFunction<as_function_t<F>, reference_t<I1>, value_type_t<I2>>() &&
    RegularFunction<as_function_t<F>, reference_t<I1>, reference_t<I2>>() &&
    RegularFunction<as_function_t<F>, iter_common_reference_t<I1>, iter_common_reference_t<I2>>();
}

template <class F, class I>
concept bool IndirectCallablePredicate() {
  return Readable<I>() &&
    Predicate<as_function_t<F>, value_type_t<I>>() &&
    Predicate<as_function_t<F>, reference_t<I>>() &&
    Predicate<as_function_t<F>, iter_common_reference_t<I>>();
}
template <class F, class I1, class I2>
concept bool IndirectCallablePredicate() {
  return Readable<I1>() && Readable<I2>() &&
    Predicate<as_function_t<F>, value_type_t<I1>, value_type_t<I2>>() &&
    Predicate<as_function_t<F>, value_type_t<I1>, reference_t<I2>>() &&
    Predicate<as_function_t<F>, reference_t<I1>, value_type_t<I2>>() &&
    Predicate<as_function_t<F>, reference_t<I1>, reference_t<I2>>() &&
    Predicate<as_function_t<F>, iter_common_reference_t<I1>, iter_common_reference_t<I2>>();
}

template <class F, class I1, class I2 = I1>
concept bool IndirectCallableRelation() {
  return Readable<I1>() && Readable<I2>() &&
    Relation<as_function_t<F>, value_type_t<I1>, value_type_t<I2>>() &&
    Relation<as_function_t<F>, value_type_t<I1>, reference_t<I2>>() &&
    Relation<as_function_t<F>, reference_t<I1>, value_type_t<I2>>() &&
    Relation<as_function_t<F>, reference_t<I1>, reference_t<I2>>() &&
    Relation<as_function_t<F>, iter_common_reference_t<I1>, iter_common_reference_t<I2>>();
}

template <class F, class I1, class I2 = I1>
concept bool IndirectCallableStrictWeakOrder() {
  return Readable<I1>() && Readable<I2>() &&
    StrictWeakOrder<as_function_t<F>, value_type_t<I1>, value_type_t<I2>>() &&
    StrictWeakOrder<as_function_t<F>, value_type_t<I1>, reference_t<I2>>() &&
    StrictWeakOrder<as_function_t<F>, reference_t<I1>, value_type_t<I2>>() &&
    StrictWeakOrder<as_function_t<F>, reference_t<I1>, reference_t<I2>>() &&
    StrictWeakOrder<as_function_t<F>, iter_common_reference_t<I1>, iter_common_reference_t<I2>>();
}

Note: These definitions of IndirectCallable and IndirectCallablePredicate are less general than the ones in N4382 that they replace. The original definitions are variadic but these handle only up to 2 arguments. The Standard Library never requires callback functions to accept more than two arguments, so the reduced expressive power does not impact the Standard Library; however, it may impact user code. The complication is the need to check callability with a cross-product of the parameters’ value_type_t and reference_ts, which is difficult to express using Concepts Lite and results in an explosion of tests to be performed as the number of parameters increases.

There are several options for preserving the full expressive power of the N4382 concepts should that prove desirable: (1) Require callability testing only with arguments “value_type_t<Is>...”, “reference_t<Is>..” , and “iter_common_reference_t<Is>...”, leaving the other combinations as merely documented constraints that are not required to be tested; (2) Actually test the full cross-product of argument types using meta-programming techniques, accepting the compile-time hit when argument lists get large. (The latter has been tested and shown to be feasable.)

Change 24.6 “Header <experimental/ranges/iterator> synopsis” ([iterator.synopsis]) by adding the following to namespace std::experimental::ranges::v1:

// Exposition only
template <class T>
concept bool _Dereferenceable =
  requires (T& t) { {*t} -> auto&&; };

// iter_move (REF)
template <class I>
  requires _Dereferenceable<I>
auto iter_move(I&& r) noexcept(see below) -> see below;

// is_indirectly_movable (REF)
template <class I1, class I2>
struct is_indirectly_movable;

// is_nothrow_indirectly_movable (REF)
template <class I1, class I2>
struct is_nothrow_indirectly_movable;

template <_Dereferenceable I>
  requires requires (I& r) { { iter_move(r) } -> auto&&; }
using rvalue_reference_t =
  decltype(iter_move(declval<I&>()));

// iter_swap (REF)
template <class I1, class I2,
  Readable _R1 = remove_reference_t<I1>,
  Readable _R2 = remove_reference_t<I2>>
  requires Swappable<reference_t<_R1>, reference_t<_R2>>()
void iter_swap(I1&& r1, I2&& r2)
  noexcept(is_nothrow_swappable_with<reference_t<_R1>, reference_t<_R2>>::value);

template <class I1, class I2,
  Readable _R1 = std::remove_reference_t<I1>,
  Readable _R2 = std::remove_reference_t<I2>>
  requires !Swappable<reference_t<_R1>, reference_t<_R2>>()
    && IndirectlyMovable<_R1, _R2>() && IndirectlyMovable<_R2, _R1>()
void iter_swap(I1&& r1, I2&& r2)
  noexcept(is_nothrow_indirectly_movable<_R1, _R2>::value &&
           is_nothrow_indirectly_movable<_R2, _R1>::value);

// is_indirectly_swappable (REF)
template <class I1, class I2 = I1>
struct is_indirectly_swappable;

// is_nothrow_indirectly_swappable (REF)
template <class I1, class I2 = I1>
struct is_nothrow_indirectly_swappable;

template <Readable I>
using iter_common_reference_t =
  common_reference_t<reference_t<I>, value_type_t<I>&>;

Before subsubsection “Iterator associated types” ([iterator.assoc]), add a
new subsubsection “Iterator utilities” ([iterator.utils]). Under that
subsubsection, insert the following:

[Editorial note: – Future work: Change iter_swap and iter_move into N4381-style customization point objects. –end note]

template <class I>
  requires _Dereferenceable<I>
auto iter_move(I&& r) noexcept(see below) -> see below;

1. The return type is Ret where Ret is
remove_reference_t<reference_t<I>>&& if I is
a reference type; decay_t<I>, otherwise.
2. The expression in the noexcept is equivalent to:

noexcept(Ret(std::move(*r)))

3. Returns: std::move(*r)

template <class I1, class I2,
  Readable _R1 = remove_reference_t<I1>,
  Readable _R2 = remove_reference_t<I2>>
  requires Swappable<reference_t<_R1>, reference_t<_R2>>()
void iter_swap(I1&& r1, I2&& r2)
  noexcept(is_nothrow_swappable_with<reference_t<_R1>, reference_t<_R2>>::value);

4. Effects: swap(*r1, *r2)

template <class I1, class I2,
  Readable _R1 = std::remove_reference_t<I1>,
  Readable _R2 = std::remove_reference_t<I2>>
  requires !Swappable<reference_t<_R1>, reference_t<_R2>>()
    && IndirectlyMovable<_R1, _R2>() && IndirectlyMovable<_R2, _R1>()
void iter_swap(I1&& r1, I2&& r2)
  noexcept(is_nothrow_indirectly_movable<_R1, _R2>::value &&
           is_nothrow_indirectly_movable<_R2, _R1>::value);

5. Effects: Exchanges values referred to by two Readable objects.

6. [Example: Below is a possible implementation:

value_type_t<_R1> tmp(iter_move(r1));
*r1 = iter_move(r2);
*r2 = std::move(tmp);

end example]

To [iterator.assoc] (24.7.1), add the following definition of rvalue_reference_t by changing this:

[…] In addition, the type

reference_t<R>

shall be an alias for decltype(*declval<R&>()).

… to this:

[…] In addition, the alias templates reference_t and rvalue_reference_t
shall be defined as follows:

template <_Dereferenceable I>
using reference_t = decltype(*declval<I&>());

template <_Dereferenceable I>
  requires requires (I& r) {
    { iter_move(r) } -> auto&&;
  }
using rvalue_reference_t =
  decltype(iter_move(declval<I&>()));

Overload resolution ([over.match]) on the expression iter_move(t) selects a
unary non-member function “iter_move” from a candidate set that includes
the function iter_move in <experimental/ranges/iterator> ([iterator.synopsis]) and
the lookup set produced by argument-dependent lookup ([basic.lookup.argdep]).

After subsubsection “Iterator operations” ([iterator.operations]), add a
new subsubsection “Iterator traits” ([iterator.traits]). Under that
subsubsection, include the following:

template <class In, class Out>
struct is_indirectly_movable : false_type { };

template <class In, class Out>
  requires IndirectlyMovable<In, Out>()
struct is_indirectly_movable<In, Out> : true_type { };

template <class In, class Out>
struct is_nothrow_indirectly_movable : false_type { };

template <class In, class Out>
  requires IndirectlyMovable<In, Out>()
struct is_nothrow_indirectly_movable<In, Out> :
  bool_constant<
    is_nothrow_constructible<value_type_t<In>, rvalue_reference_t<In>>::value &&
    is_nothrow_assignable<value_type_t<In> &, rvalue_reference_t<In>>::value &&
    is_nothrow_assignable<reference_t<Out>, rvalue_reference_t<In>>::value &&
    is_nothrow_assignable<reference_t<Out>, value_type_t<In>>::value>
{ };

template <class I1, class I2 = I1>
struct is_indirectly_swappable : false_type { };

template <class I1, class I2>
  requires IndirectlySwappable<I1, I2>()
struct is_indirectly_swappable<I1, I2> : true_type { };

template <class I1, class I2 = I1>
struct is_nothrow_indirectly_swappable : false_type { };

template <class I1, class I2>
  requires IndirectlySwappable<I1, I2>()
struct is_nothrow_indirectly_swappable<I1, I2> :
  bool_constant<
    noexcept(iter_swap(declval<I1&>(), declval<I2&>())) &&
    noexcept(iter_swap(declval<I2&>(), declval<I1&>())) &&
    noexcept(iter_swap(declval<I1&>(), declval<I1&>())) &&
    noexcept(iter_swap(declval<I2&>(), declval<I2&>()))>
{ };

1. Overload resolution ([over.match]) on the four expressions iter_move(x, y) selects a
binary non-member function “iter_swap” from a candidate set that includes
the two functions iter_swap in <experimental/ranges/iterator> ([iterator.synopsis]) and
the lookup set produced by argument-dependent lookup ([basic.lookup.argdep]).

Change 25.1 “Algorithms: General” ([algorithms.general]) as follows:


template<InputIterator I, Sentinel<I> S, WeaklyIncrementable O,
    class Proj = identity, IndirectCallableRelation<projected<I, Proj>> R = equal_to<>>
  requires IndirectlyCopyable<I, O>() && (ForwardIterator<I>() ||
    ForwardIterator<O>() || Copyable<value_type_t<I>>())
  tagged_pair<tag::in(I), tag::out(O)>
    unique_copy(I first, S last, O result, R comp = R{}, Proj proj = Proj{});

template<InputRange Rng, WeaklyIncrementable O, class Proj = identity,
    IndirectCallableRelation<projected<IteratorType<Rng>, Proj>> R = equal_to<>>
  requires IndirectlyCopyable<IteratorType<Rng>, O>() &&
    (ForwardIterator<IteratorType<Rng>>() || ForwardIterator<O>()
      || Copyable<value_type_t<IteratorType<Rng>>>())
  tagged_pair<tag::in(safe_iterator_t<Rng>), tag::out(O)>
    unique_copy(Rng&& rng, O result, R comp = R{}, Proj proj = Proj{});

Make the identical change to 25.3.9 “Unique” ([alg.unique]).

7 Acknowledgements

I would like to extend my sincerest gratitude to Sean Parent, Andrew Sutton,
and Casey Carter for their help formalizing and vetting many of the ideas
presented in this paper and in the Ranges TS.

I would also like to thank Herb Sutter and the Standard C++ Foundation, without
whose generous financial support this work would not be possible.

References

[1] Abrahams, D. et al. 2004. N1640: New Iterator Concepts.

[2] CMCSTL2: https://github.com/CaseyCarter/cmcstl2. Accessed: 2015-09-09.

[3] Dietmar, K. and Abrahams, D. 2005. N1873: The Cursor/Property Map Abstraction.

[4] Niebler, E. 2015. N4382: Working Draft: C++ Extensions for Ranges.

[5] Niebler, E. 2015. Suggested Design for Customization Points.

[6] Range v3: http://www.github.com/ericniebler/range-v3. Accessed: 2014-10-08.

[7] SGI Standard Template Library Programmer’s Guide: https://www.sgi.com/tech/stl/. Accessed: 2015-08-12.

[8] Stroustrup, B. and Sutton, A. 2012. N3351: A Concept Design for the STL.

[9] Sutter, H. 1999. When is a container not a container? C++ Report. 11, 5 (May 1999).

1 Appendix 1: Reference implementations of common_type and common_reference

#include <utility>
#include <type_traits>

using std::is_same;
using std::decay_t;
using std::declval;

template <class T>
using __t = typename T::type;

template <class T>
constexpr typename __t<T>::value_type __v = __t<T>::value;

template <class T, class... Args>
using __apply = typename T::template apply<Args...>;

template <class T, class U>
struct __compose {
  template <class V>
  using apply = __apply<T, __apply<U, V>>;
};

template <class T>
struct __id { using type = T; };

template <template <class...> class T, class... U>
concept bool _Valid = requires { typename T<U...>; };

template <class U, template <class...> class T, class... V>
concept bool _Is = _Valid<T, U, V...> && __v<T<U, V...>>;

template <class U, class V>
concept bool _ConvertibleTo = _Is<U, std::is_convertible, V>;

template <template <class...> class T, class... U>
struct __defer { };
_Valid{T, ...U}
struct __defer<T, U...> : __id<T<U...>> { };

template <template <class...> class T>
struct __q {
  template <class... U>
  using apply = __t<__defer<T, U...>>;
};

template <class T>
struct __has_type : std::false_type { };
template <class T> requires _Valid<__t, T>
struct __has_type<T> : std::true_type { };

template <class T, class X = std::remove_reference_t<T>>
using __cref = std::add_lvalue_reference_t<std::add_const_t<X>>;
template <class T>
using __uncvref = std::remove_cv_t<std::remove_reference_t<T>>;
template <class T, class U, class R = __builtin_common_t<T &, U &>>
using __rref_res = std::conditional_t<__v<std::is_reference<R>>,
  std::remove_reference_t<R> &&, R>;
template <class T, class U>
using __cond_res = decltype(true ? declval<T>() : declval<U>());

template <class From, class To>
struct __copycv_ : __id<To> { };
template <class From, class To>
struct __copycv_<From const, To> : std::add_const<To> { };
template <class From, class To>
struct __copycv_<From volatile, To> : std::add_volatile<To> { };
template <class From, class To>
struct __copycv_<From const volatile, To> : std::add_cv<To> { };
template <class From, class To>
using __copycv = __t<__copycv_<From, To>>;

template <class T, class U>
struct __builtin_common { };
template <class T, class U>
using __builtin_common_t = __t<__builtin_common<T, U>>;
template <class T, class U>
  requires _Valid<__cond_res, __cref<T>, __cref<U>>
struct __builtin_common<T, U> :
  std::decay<__cond_res<__cref<T>, __cref<U>>> { };
template <class T, class U>
  requires _Valid<__builtin_common_t, T &, U &>
    && _ConvertibleTo<T &&, __rref_res<T, U>>
    && _ConvertibleTo<U &&, __rref_res<T, U>>
struct __builtin_common<T &&, U &&> : __id<__rref_res<T, U>> { };
template <class T, class U>
using __lref_res = __cond_res<__copycv<T, U> &, __copycv<U, T> &>;
template <class T, class U>
struct __builtin_common<T &, U &> : __defer<__lref_res, T, U> { };
template <class T, class U>
  requires _Valid<__builtin_common_t, T &, U const &>
    && _ConvertibleTo<U &&, __builtin_common_t<T &, U const &>>
struct __builtin_common<T &, U &&> :
  __builtin_common<T &, U const &> { };
template <class T, class U>
struct __builtin_common<T &&, U &> : __builtin_common<U &, T &&> { };

// common_type
template <class ...Ts>
struct common_type { };

template <class... T>
using common_type_t = __t<common_type<T...>>;

template <class T>
struct common_type<T> : std::decay<T> { };

template <class T>
concept bool _Decayed = __v<is_same<decay_t<T>, T>>;

template <class T, class U>
struct __common_type2
  : common_type<decay_t<T>, decay_t<U>> { };

template <_Decayed T, _Decayed U>
struct __common_type2<T, U> : __builtin_common<T, U> { };

template <class T, class U>
struct common_type<T, U> : __common_type2<T, U> { };

template <class T, class U, class V, class... W>
  requires _Valid<common_type_t, T, U>
struct common_type<T, U, V, W...>
  : common_type<common_type_t<T, U>, V, W...> { };

namespace __qual {
  using __rref = __q<std::add_rvalue_reference_t>;
  using __lref = __q<std::add_lvalue_reference_t>;
  template <class>
  struct __xref : __id<__compose<__q<__t>, __q<__id>>> { };
  template <class T>
  struct __xref<T&> : __id<__compose<__lref, __t<__xref<T>>>> { };
  template <class T>
  struct __xref<T&&> : __id<__compose<__rref, __t<__xref<T>>>> { };
  template <class T>
  struct __xref<const T> : __id<__q<std::add_const_t>> { };
  template <class T>
  struct __xref<volatile T> : __id<__q<std::add_volatile_t>> { };
  template <class T>
  struct __xref<const volatile T> : __id<__q<std::add_cv_t>> { };
}

template <class T, class U, template <class> class TQual,
  template <class> class UQual>
struct basic_common_reference { };

template <class T, class U>
using __basic_common_reference =
  basic_common_reference<__uncvref<T>, __uncvref<U>,
    __qual::__xref<T>::type::template apply,
    __qual::__xref<U>::type::template apply>;

// common_reference
template <class... T>
struct common_reference { };

template <class... T>
using common_reference_t = __t<common_reference<T...>>;

template <class T>
struct common_reference<T> : __id<T> { };

template <class T, class U>
struct __common_reference2
  : std::conditional_t<__v<__has_type<__basic_common_reference<T, U>>>,
      __basic_common_reference<T, U>, common_type<T, U>> { };

template <class T, class U>
  requires _Valid<__builtin_common_t, T, U>
    && _Is<__builtin_common_t<T, U>, std::is_reference>
struct __common_reference2<T, U> : __builtin_common<T, U> { };

template <class T, class U>
struct common_reference<T, U> : __common_reference2<T, U> { };

template <class T, class U, class V, class... W>
  requires _Valid<common_reference_t, T, U>
struct common_reference<T, U, V, W...>
  : common_reference<common_reference_t<T, U>, V, W...> { };