Closed CaseyCarter closed 8 years ago
Possible solutions:
Introduce additional overloads that require SizedIteratorRange
:
SizedIteratorRange{I, S}
void advance(I& i, S bound);
SizedIteratorRange{I, S}
DifferenceType<I> advance(I& i, DifferenceType<I> n, S bound);
SizedIteratorRange{I, S}
DifferenceType<I> distance(I first, S last);
and similarly for the overloads of next
and prev
that are specified to dispatch to advance
, and any algorithms that may use the pertinent overloads of advance
or distance
or next
or prev
in their implementations.
Iterator
/ Sentinel
pairs that meet the syntactic requirements of SizedIteratorRange
but do not satisfy SizedIteratorRange
.The "introduce additional overloads" solution is going to have scalability issues when we get into the algorithms. Any feature like SizedIteratorRange
/ SizedRange
of which we want to allow algorithm implementors to take advantage will require the introduction of an additional set of permuted declarations. For example, search
:
template<ForwardIterator I1, Sentinel<I1> S1, ForwardIterator I2, Sentinel<I2> S2,
class Pred = equal_to<>, class Proj1 = identity, class Proj2 = identity>
requires IndirectlyComparable<I1, I2, Pred, Proj1, Proj2>()
I1 search(I1 first1, S1 last1, I2 first2, S2 last2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
template<ForwardRange Rng1, ForwardRange Rng2, class Pred = equal_to<>,
class Proj1 = identity, class Proj2 = identity>
requires IndirectlyComparable<IteratorType<Rng1>, IteratorType<Rng2>, Pred, Proj1, Proj2>()
safe_iterator_t<Rng1>
search(Rng1&& rng1, Rng2&& rng2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
needs additional declarations:
template<ForwardIterator I1, Sentinel<I1> S1, ForwardIterator I2, Sentinel<I2> S2,
class Pred = equal_to<>, class Proj1 = identity, class Proj2 = identity>
requires IndirectlyComparable<I1, I2, Pred, Proj1, Proj2>() &&
SizedIteratorRange<I1, S1>()
I1 search(I1 first1, S1 last1, I2 first2, S2 last2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
template<ForwardIterator I1, Sentinel<I1> S1, ForwardIterator I2, Sentinel<I2> S2,
class Pred = equal_to<>, class Proj1 = identity, class Proj2 = identity>
requires IndirectlyComparable<I1, I2, Pred, Proj1, Proj2>() &&
SizedIteratorRange<I2, S2>()
I1 search(I1 first1, S1 last1, I2 first2, S2 last2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
template<ForwardIterator I1, Sentinel<I1> S1, ForwardIterator I2, Sentinel<I2> S2,
class Pred = equal_to<>, class Proj1 = identity, class Proj2 = identity>
requires IndirectlyComparable<I1, I2, Pred, Proj1, Proj2>() &&
SizedIteratorRange<I1, S1>() && SizedIteratorRange<I2, S2>()
I1 search(I1 first1, S1 last1, I2 first2, S2 last2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
template<ForwardRange Rng1, ForwardRange Rng2, class Pred = equal_to<>,
class Proj1 = identity, class Proj2 = identity>
requires SizedRange<Rng1>() &&
IndirectlyComparable<IteratorType<Rng1>, IteratorType<Rng2>, Pred, Proj1, Proj2>()
safe_iterator_t<Rng1>
search(Rng1&& rng1, Rng2&& rng2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
template<ForwardRange Rng1, ForwardRange Rng2, class Pred = equal_to<>,
class Proj1 = identity, class Proj2 = identity>
requires SizedRange<Rng2>() &&
IndirectlyComparable<IteratorType<Rng1>, IteratorType<Rng2>, Pred, Proj1, Proj2>()
safe_iterator_t<Rng1>
search(Rng1&& rng1, Rng2&& rng2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
template<ForwardRange Rng1, ForwardRange Rng2, class Pred = equal_to<>,
class Proj1 = identity, class Proj2 = identity>
requires SizedRange<Rng1>() && SizedRange<Rng2>() &&
IndirectlyComparable<IteratorType<Rng1>, IteratorType<Rng2>, Pred, Proj1, Proj2>()
safe_iterator_t<Rng1>
search(Rng1&& rng1, Rng2&& rng2, Pred pred = Pred{},
Proj1 proj1 = Proj1{}, Proj2 proj2 = Proj2{});
and since the Range
overloads dispatch to the Iterator
overloads, and it's possible for a range that doesn't satisfy SizedRange
to have an IteratorType
and SentinelType
that satisfy SizedIteratorRange
, all of those Range
overload variants also need additional !SizedRange<R>() && SizedIteratorRange<IteratorType<R>, SentinelType<R>>()
variants as well.
When we added SizedRange{R} DistanceType<IteratorType<R>> distance(R&&);
to solve this problem for Range{R} DistanceType<IteratorType<R>> distance(R&&);
it seemed like a good idea, but I don't think the approach is tenable for the algorithms.
I don't think the approach is tenable for the algorithms.
I think the correct approach here is to have some text in the library introduction describing what happens when instantiating a template on a type that does not satisfy the requirements: undefined behavior. We would also need to extend this blanket wording to function templates that are specified to use concepts internally to dispatch to optimal implementations.
I greatly prefer a single consistent library-wide requirement for SizedRange
and SizedIteratorRange
. Your suggestion implies that we decide at specification time to partition the library into components that may used SizedRange
/ SizedIteratorRange
in their implementations, and components that may not - restricting implementor freedom. We either specify the partitioning explicitly - increasing specification size and thus cognitive load on readers - or leave it implicit so users must recursively trace requirements through the standard to determine the partitioning.
I think it would save everyone time and trouble if we rename disable_sized_range
to disable_sized
, and require users who pass Range
s or Sentinel
s to the library that do not satisfy SizedRange
/ SizedIteratorRange
but do meet the syntactic requirements to specialize disable_sized
to false
.
I think it would save everyone time and trouble if we rename
disable_sized_range
todisable_sized
, and require users who passRange
s orSentinel
s to the library that do not satisfySizedRange
/SizedIteratorRange
bit do meet the syntactic requirements to specializedisable_sized
tofalse
.
What would the definition of such a disable_sized
trait look like?
The trait itself would be the same except for the name change; when we made it a pure opt-out trait it became agnostic to the type of the parameter. It's now simply a mapping from type to bool
that defaults to false
(which may as well be a variable template, now that I think about it). What would be different is the definition of SizedIteratorRange
to allow opt-out:
template <class I, class S>
concept bool SizedIteratorRange() {
return Sentinel<S, I>() &&
!disable_sized<S>::value &&
requires (const I i, const S j) {
{ i - i } -> DifferenceType<I>;
{ j - j } -> DifferenceType<I>;
{ i - j } -> DifferenceType<I>;
{ j - i } -> DifferenceType<I>;
};
}
Although it seems a bit hackish that only the sentinel type participates in the determination. I'm convinced that we need a single library-wide requirement, but I'm not certain at this time exactly what that requirement should be. I need to let this gel some more.
Obviously, sized-ness is not a property of the sentinel only, but of the iterator/sentinel pair. Pressing a single trait into service for this will require a fairly contrived and confusing interface. It requires more thought.
Describing:
[iterator.operations]/7 says:
Paragraph 10 similarly requires of:
Paragraph 12 provides a similar requirement for
Since it is not possible for a library implementation to determine if a set of parameters satisfies the non-syntactic requirements of a concept, these requirements are not implementable.