Document number: | P0022R2 |
Date: | 2016-10-17 |
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
This paper presents an extension to the Ranges design[5] 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.
Everything suggested here has been implemented in C++11, where Concepts has been simulated with the help of generalized SFINAE for expressions. In addition, a full implementation using Concepts exists[2] that works with the “-std=c++1z
” support in gcc trunk.
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 bool
s as bits in packed integers rather than as actual bool
s, 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:
vector
is required to have random-access iterators), andLooking 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:
zip
view of N sequences (described below)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.
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.
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
andIndirectlyCopyable
concepts describe copy and move relationships
between the values of an input iterator,I
, and an output iteratorOut
. For an output iterator
out
and an input iteratorin
, their syntactic requirements expand to:
IndirectlyMovable
requires*out = move(*in)
The iterators into a non-const vector
are Permutable
. If we zip
the two vector
s 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).
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 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.
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).
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.
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.
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.
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.)
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 tuple
s 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.
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.
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(static_cast<__iter_move_t<I>>(*r))) {
return static_cast<__iter_move_t<I>>(*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:
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>>() &&
IndirectlyMovableStorable<I1, I2>() &&
IndirectlyMovableStorable<I2, I1>()
void iter_swap(I1 r1, I2 r2)
noexcept(is_nothrow_indirectly_movable_v<I1, I2> &&
is_nothrow_indirectly_movable_v<I2, I1>) {
value_type_t<I1> tmp = iter_move(r1);
*r1 = iter_move(r2);
*r2 = std::move(tmp);
}
See below for the updated IndirectlyMovable
concept.
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.
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>&>;
With overloads of iter_swap
that work for Swappable
types and IndirectlyMovableStorable
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);
};
}
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>>
:
Predicate<F, value_type_t<I>, value_type_t<I>>
Predicate<F, value_type_t<I>, reference_t<I>>
Predicate<F, reference_t<I>, value_type_t<I>>
Predicate<F, reference_t<I>, reference_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:
Predicate<F, iter_common_reference_t<I>, iter_common_reference_t<I>>
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;});
move
a customization pointRather 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[4] 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.
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.
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.
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.
This section is written as a set of diffs against N4569, “C++ Extensions for Ranges” and N4141 (C++14), except where otherwise noted.
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 typeC
such that bothConvertibleTo<T, C>()
andConvertibleTo<U, C>()
are satisfied, thenT
andU
share a common reference type,C
. [ Note:C
could be the same asT
, orU
, 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
bestd::common_reference_t<T, U>
. Lett
be a function whose return type isT
, and letu
be a function whose return type isU
.CommonReference<T, U>()
is satisfied if and only if(2.1) –
C(t())
equalsC(t())
if and only ift
is an equality preserving function ([19.1.1] REF(concepts.lib.general.equality)).
(2.2) –C(u())
equalsC(u())
if and only ifu
is an equality preserving function ([19.1.1] REF(concepts.lib.general.equality)).3. [ Note: Users are free to specialize
common_reference
andbasic_common_reference
when at least one parameter depends on a user-defined type. Those specializations are considered by theCommonReference
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
becommon_type_t<T, U>
. Lett
be a function whose return type isT
, and letu
be a function whose return type isU
.Common<T, U>()
is satisfied if and only if(2.1) –
C(t())
equalsC(t())
if and only ift
is an equality preserving function ([19.1.1] REF(concepts.lib.general.equality)).
(2.2) –C(u())
equalsC(u())
if and only ifu
is an equality preserving 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 theCommon
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>>
.
Change [meta.type.synop], header <type_traits>
synopsis, as indicated (N.B., in namespace std
):
is_[nothrow_]swappable[_with]
traits taken from the current Working Draft of C++17. –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; […] // 20.15.4.3, type properties […] template <class T, class U> constexpr bool is_swappable_with_v = is_swappable_with<T, U>::value; template <class T> constexpr bool is_swappable_v = is_swappable<T>::value; […] template <class T, class U> constexpr bool is_nothrow_swappable_with_v = is_nothrow_swappable_with<T, U>::value; template <class T> constexpr bool is_nothrow_swappable_v = is_nothrow_swappable<T>::value; […] }
Change [meta.unary.prop], Table 49 — “Type property predicates”, as indicated:
Change Table 57 Other Transformations as follows:
Table 57 — Other transformations 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
specializationisdepends 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_reference —end 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)
beadd_lvalue_reference_t<const remove_reference_t<A>>
. LetUNCVREF(A)
beremove_cv_t<remove_reference_t<A>>
. LetXREF(A)
denote a unary templateT
such thatT<UNCVREF(A)>
denotes the same type asA
. LetCOPYCV(FROM, TO)
be an alias for typeTO
with the addition ofFROM
’s top-level cv-qualifiers. [Example: –COPYCV(const int, volatile short)
is an alias forconst volatile short
. – end example] LetRREF_RES(Z)
beremove_reference_t<Z>&&
ifZ
is a reference type orZ
otherwise. LetCOND_RES(X, Y)
bedecltype(declval<bool>() ? declval<X>() : declval<Y>())
. Given typesA
andB
, letX
beremove_reference_t<A>
, letY
beremove_reference_t<B>
, and letCOMMON_REF(A, B)
be:(3.1) – If
A
andB
are both lvalue reference types,COMMON_REF(A, B)
isCOND_RES(COPYCV(X, Y) &, COPYCV(Y, X) &)
.
(3.2) – Otherwise, letC
beRREF_RES(COMMON_REF(X&, Y&))
. IfA
andB
are both rvalue reference types, andC
is well-formed, andis_convertible<A, C>::value
andis_convertible<B, C>::value
are true, thenCOMMON_REF(A, B)
isC
.
(3.3) – Otherwise, letD
beCOMMON_REF(const X&, Y&)
. IfA
is an rvalue reference andB
is an lvalue reference andD
is well-formed andis_convertible<A, D>::value
istrue
, thenCOMMON_REF(A, B)
isD
.
(3.4) – Otherwise, ifA
is an lvalue reference andB
is an rvalue reference, thenCOMMON_REF(A, B)
isCOMMON_REF(B, A)
.
(3.5) – Otherwise,COMMON_REF(A, B)
isdecay_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 thecommon_type
trait applied to a parameter packT
of types, the membertype
shall be either defined or not present as follows:(4.1) – If
sizeof...(T)
is zero, there shall be no membertype
.
(4.2) – Otherwise, ifsizeof...(T)
is one, letT0
T1
denote the sole type in the packT
. The member typedeftype
shall denote the same type asdecay_t<
T0
T1
>
.
(4.3) – Otherwise, ifsizeof...(T)
is two, letT1
andT2
denote the two types in the packT
, and letD1
andD2
bedecay_t<T1>
anddecay_t<T2>
respectively. Then(4.3.1) – If
D1
andT1
denote the same type andD2
andT2
denote the same type, then(4.3.1.1) – If
COMMON_REF(T1, T2)
is well-formed, then the member typedeftype
denotes that type.
(4.3.1.2) – Otherwise, there shall be no membertype
.(4.3.2) – Otherwise, if
common_type_t<D1, D2>
is well-formed, then the member typedeftype
denotes that type.
(4.3.3) – Otherwise, there shall be no membertype
.(4.4) – Otherwise, if
sizeof...(T)
is greater than onetwo, letT1
,T2
, andR
est
, respectively, denote the first, second, and (pack of) remaining types comprisingT
. [ Note:sizeof...(R)
may be zero. –end note ] LetC
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 typecommon_type_t<T1, T2>
. Then(4.4.1) – If there is such a type
C
, the member typedeftype
shall denote the same type, if any, ascommon_type_t<C, R
est
...>
.
(4.4.2) – Otherwise, there shall be no membertype
.5. For the
common_reference
trait applied to a parameter packT
of types, the membertype
shall be either defined or not present as follows:(5.1) – If
sizeof...(T)
is zero, there shall be no membertype
.
(5.2) – Otherwise, ifsizeof...(T)
is one, letT1
denote the sole type in the packT
. The member typedeftype
shall denote the same type asT1
.
(5.3) – Otherwise, ifsizeof...(T)
is two, letT1
andT2
denote the two types in the packT
. Then(5.3.1) – If
COMMON_REF(T1, T2)
is well-formed and denotes a reference type then the member typedeftype
denotes that type.
(5.3.2) – Otherwise, ifbasic_common_reference<UNCVREF(T1), UNCVREF(T2), XREF(T1), XREF(T2)>::type
is well-formed, then the member typedeftype
denotes that type.
(5.3.3) – Otherwise, ifcommon_type_t<T1, T2>
is well-formed, then the member typedeftype
denotes that type.
(5.3.4) – Otherwise, there shall be no membertype
.(5.4) – Otherwise, if
sizeof...(T)
is greater than two, letT1
,T2
, andRest
, respectively, denote the first, second, and (pack of) remaining types comprisingT
. LetC
be the typecommon_reference_t<T1, T2>
. Then(5.4.1) – If there is such a type
C
, the member typedeftype
shall denote the same type, if any, ascommon_reference_t<C, Rest...>
.
(5.4.2) – Otherwise, there shall be no membertype
.
Change concept Readable
([iterators.readable]) 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>>; { ranges::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>>(); }
Change “Indirect callables” ([indirectcallables.indirectfunc]) as described as follows: Change IndirectCallable
, IndirectRegularCallable
, IndirectCallablePredicate
, IndirectCallableRelation
, IndirectCallableStrictWeakOrder
, and indirect_result_of
as follows:
template <class F> concept bool IndirectCallable() { return Callable<F>(); } template <class F, class I> concept bool IndirectCallable() { return Readable<I>() && Callable<F, value_type_t<I>>() && Callable<F, reference_t<I>>() && Callable<F, iter_common_reference_t<I>>(); } template <class F, class I1, class I2> concept bool IndirectCallable() { return Readable<I1>() && Readable<I2>() && Callable<F, value_type_t<I1>, value_type_t<I2>>() && Callable<F, value_type_t<I1>, reference_t<I2>>() && Callable<F, reference_t<I1>, value_type_t<I2>>() && Callable<F, reference_t<I1>, reference_t<I2>>() && Callable<F, iter_common_reference_t<I1>, iter_common_reference_t<I2>>(); } template <class F> concept bool IndirectRegularCallable() { return RegularCallable<F>(); } template <class F, class I> concept bool IndirectRegularCallable() { return Readable<I>() && RegularCallable<F, value_type_t<I>>() && RegularCallable<F, reference_t<I>>() && RegularCallable<F, iter_common_reference_t<I>>(); } template <class F, class I1, class I2> concept bool IndirectRegularCallable() { return Readable<I1>() && Readable<I2>() && RegularCallable<F, value_type_t<I1>, value_type_t<I2>>() && RegularCallable<F, value_type_t<I1>, reference_t<I2>>() && RegularCallable<F, reference_t<I1>, value_type_t<I2>>() && RegularCallable<F, reference_t<I1>, reference_t<I2>>() && RegularCallable<F, iter_common_reference_t<I1>, iter_common_reference_t<I2>>(); } template <class F, class I> concept bool IndirectCallablePredicate() { return Readable<I>() && Predicate<F, value_type_t<I>>() && Predicate<F, reference_t<I>>() && Predicate<F, iter_common_reference_t<I>>(); } template <class F, class I1, class I2> concept bool IndirectCallablePredicate() { 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>>(); } template <class F, class I1, class I2 = I1> concept bool IndirectCallableRelation() { return Readable<I1>() && Readable<I2>() && Relation<F, value_type_t<I1>, value_type_t<I2>>() && Relation<F, value_type_t<I1>, reference_t<I2>>() && Relation<F, reference_t<I1>, value_type_t<I2>>() && Relation<F, reference_t<I1>, reference_t<I2>>() && Relation<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<F, value_type_t<I1>, value_type_t<I2>>() && StrictWeakOrder<F, value_type_t<I1>, reference_t<I2>>() && StrictWeakOrder<F, reference_t<I1>, value_type_t<I2>>() && StrictWeakOrder<F, reference_t<I1>, reference_t<I2>>() && StrictWeakOrder<F, iter_common_reference_t<I1>, iter_common_reference_t<I2>>(); } template <class> struct indirect_result_of { }; template <class F, class... Is> requires IndirectCallable<F, Is...>() struct indirect_result_of<F(Is...)> : result_of<F(reference_t<Is>...)> { };
Note: These definitions of IndirectCallable
and IndirectCallablePredicate
are less general than the ones in N4569 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_t
s, which is difficult to express using Concepts 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 N4569 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 the definition of the projected
struct ([projected]) to the following:
template <Readable I, IndirectRegularCallable<I> Proj> struct projected { using value_type = decay_t<indirect_result_of_t<Proj&(I)>>; indirect_result_of_t<Proj&(I)> operator*() const; };
Remove the definition of the rvalue_reference_t
type alias in IndirectlyMovable
([commonalgoreq.indirectlymovable]).
Change concept IndirectlySwappable
([commonalgoreq.indirectlyswappable]) 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) { ranges::iter_swap(i1, i2); ranges::iter_swap(i2, i1); ranges::iter_swap(i1, i1); ranges::iter_swap(i2, i2); }; }
Change the description of IndirectlySwappable
:
1. Given an object
i1
of typeI1
and an objecti2
of
typeI2
,IndirectlySwappable<I1, I2>()
is satisfied if after
ranges::iter_swap(i1, i2)
, the value of*i1
is equal to the
value of*i2
before the call, and vice versa.
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) namespace { constexpr *unspecified* iter_move = *unspecified*; } // 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 <class I1, class I2> constexpr bool is_indirectly_movable_v = is_indirectly_movable<I1, I2>::value; template <class I1, class I2> constexpr bool is_nothrow_indirectly_movable_v = is_nothrow_indirectly_movable<I1, I2>::value; template <_Dereferenceable I> requires requires (I& r) { { ranges::iter_move(r) } -> auto&&; } using rvalue_reference_t = decltype(ranges::iter_move(declval<I&>())); // iter_swap (REF) namespace { constexpr *unspecified* iter_swap = *unspecified*; } // 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:
24.6.? iter_move [iterator.utils.iter_move]
1. The name
iter_move
denotes a customization point object (17.5.2.1.5 [customization.point.object]). The effect of the expressionranges::iter_move(E)
for some expressionE
is equivalent toiter_move(E)
, with overload resolution performed in a context described below.2. The context in which the expression
iter_move(E)
is evaluated– does not include a declaration of
ranges::iter_move
,– includes the lookup set produced by argument-dependent lookup ([basic.lookup.argdep]), and
– includes an
iter_move
function template equivalent to the following:// Exposition only template <_Dereferenceable I> *see below* iter_move(I&& r) noexcept(*see below*);
2.1. The return type is
Ret
whereRet
is
remove_reference_t<reference_t<I>>&&
if
reference_t<I>
is a reference type;
decay_t<reference_t<I>>
, otherwise.2.2. The expression in the
noexcept
is equivalent to:noexcept(static_cast<Ret>(*r))
2.3. Effects: Equivalent to
static_cast<Ret>(*r)
3. If, for the
iter_move
function selected by overload resolution ([over.match]),iter_move(r)
does not equal*r
, the program is ill-formed with no diagnostic required.24.6.? iter_swap [iterator.utils.iter_swap]
1. The name
iter_swap
denotes a customization point object (17.5.2.1.5 [customization.point.object]). The effect of the expressionranges::iter_swap(E1, E2)
for some expressionsE1
andE2
is equivalent to(void)iter_swap(E1, E2)
, with overload resolution performed in a context described below:2. The context in which the expression
(void)iter_swap(E1, E2)
is evaluated– does not include a declaration of
ranges::iter_swap
,– includes the lookup set produced by argument-dependent lookup ([basic.lookup.argdep]), and
– includes
iter_swap
function template(s) equivalent to the following:// Exposition only template <class I1, class I2> void iter_swap(I1&&, I2&&) = delete; // Exposition only 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_v<reference_t<_R1>, reference_t<_R2>>);
2.1. Effects: Equivalent to
ranges::swap(*r1, *r2)
// Exposition only 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>>() && IndirectlyMovableStorable<_R1, _R2>() && IndirectlyMovableStorable<_R2, _R1>() void iter_swap(I1&& r1, I2&& r2) noexcept(is_nothrow_indirectly_movable_v<_R1, _R2> && is_nothrow_indirectly_movable_v<_R2, _R1>);
2.2. Effects: Exchanges values referred to by two
Readable
objects.2.3. [Example: Below is a possible implementation:
value_type_t<_R1> tmp(iter_move(r1)); *r1 = iter_move(r2); *r2 = std::move(tmp);
– end example]
3. If the
iter_swap
function selected by overload resolution ([over.match]) does not swap the values denoted by the expressionsE1
andE2
, the program is ill-formed with no diagnostic required.
Change the name of subsection “Iterator associated types” (24.6.1) [iterator.assoc] back to “Iterator traits”. To that section 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
andrvalue_reference_t
shall be defined as follows:template <_Dereferenceable I> using reference_t = decltype(*declval<I&>()); template <_Dereferenceable I> requires requires (I& r) { { ranges::iter_move(r) } -> auto&&; } using rvalue_reference_t = decltype(ranges::iter_move(declval<I&>()));
After paragraph 12, add the following paragraph 13:
13. The class templates
is_indirectly_movable
,
is_nothrow_indirectly_movable
,is_indirectly_swappable
,
andis_nothrow_indirectly_swappable
shall be defined
as follows: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> : std::integral_constant<bool, 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> : std::integral_constant<bool, noexcept(ranges::iter_swap(declval<I1&>(), declval<I2&>())) && noexcept(ranges::iter_swap(declval<I2&>(), declval<I1&>())) && noexcept(ranges::iter_swap(declval<I1&>(), declval<I1&>())) && noexcept(ranges::iter_swap(declval<I2&>(), declval<I2&>()))> { };
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.
[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. Suggested Design for Customization Points.
[5] Niebler, E. and Carter, C. 2016. N4569: Working Draft: C++ Extensions for Ranges.
[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).
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...> { };