| Document #: | P1871R0 |
| Date: | 2019-10-06 |
| Project: | Programming Language C++ LEWG |
| Reply-to: |
Barry Revzin <barry.revzin@gmail.com> |
The sized_range concept is currently defined as follows, in [range.sized]:
template<class T>
concept sized_range =
range<T> &&
!disable_sized_range<remove_cvref_t<T>> &&
requires(T& t) { ranges::size(t); };The reason for the extra !disable_sized_range<remove_cvref_t<T>> check is that some types can meet the syntactic requirements of ranges::size without meeting the semantic requirement that this call must have constant time. For instance, a pre-C++11 std::list had O(N) size(), but this wouldn’t be detectable, so the type trait exists to allow for such containers to opt out of being considered sized_ranges.
A similar observation can be made for the sized_sentinel_for concept, from [iterator.concept.sizedsentinel]:
template<class S, class I>
concept sized_sentinel_for =
sentinel_for<S, I> &&
!disable_sized_sentinel<remove_cv_t<S>, remove_cv_t<I>> &&
requires(const I& i, const S& s) {
{ s - i } -> same_as<iter_difference_t<I>>;
{ i - s } -> same_as<iter_difference_t<I>>;
};On the flip side, we also have the view concept in [range.view]:
Two negated type traits for disabling, one positive one to enable. Why both directions?
One argument that you could make comes from usage. Pretty much all types that meet the syntactic requirements for sized_range and sized_sentinel do in fact model those concepts. It’s only a few oddballs that need to explicitly opt-out of those concepts. On the other hand, most types that meet the syntactic requirements for view aren’t actually views - and those need to opt-in.
A different argument can be: what does it mean to specialize these traits? If you specialize disable_sized_sentinel to be true, that type is definitely not a sized_sentinel. But if you specialize enable_view to be true, that type isn’t necessarily a view - it’s just that you checked that one box. It’s not as final a statement.
But ultimately, double negatives are needlessly difficult to understand. We say a type models sized_range or it does not model sized_range. We do not say a type does not not model sized_range. We really should try to avoid double negatives whenever possible.
The real problem is that we need this variable template to begin with - but that’s a separate, language problem. If we rename disable_sized_range to enable_sized_range and disable_sized_sentinel to enable_sized_sentinel_for, then all of our type trait variable templates are spelled the same way (enable_concept_name).
The traits will have different defaults, but are defined such that they should rarely need to be touched anyway (enable_view has a heuristic trying to be right most of the time, and the other two in this new formulation would almost always be true).
The proposal is to flip the two disabling variable templates to enabling variable templates: renaming disable_sized_range to enable_sized_range and disable_sized_sentinel to enable_sized_sentinel_for (note the extra _for). And then update all usage of them to be positive rather than negative.
Change 23.2 [iterator.synopsis]:
and later:
template<class Iterator1, class Iterator2> requires (!sized_sentinel_for<Iterator1, Iterator2>) - inline constexpr bool disable_sized_sentinel<reverse_iterator<Iterator1>, - reverse_iterator<Iterator2>> = true; + inline constexpr bool enable_sized_sentinel_for<reverse_iterator<Iterator1>, + reverse_iterator<Iterator2>> = false;
Change 23.3.4.8 [iterator.concept.sizedsentinel]:
1 The
sized_sentinel_forconcept specifies requirements on aninput_or_output_iteratorand a correspondingsentinel_forthat allow the use of the-operator to compute the distance between them in constant time.template<class S, class I> concept sized_sentinel_for = sentinel_for<S, I> && - !disable_sized_sentinel<remove_cv_t<S>, remove_cv_t<I>> && + enable_sized_sentinel_for<remove_cv_t<S>, remove_cv_t<I>> && requires(const I& i, const S& s) { { s - i } -> same_as<iter_difference_t<I>>; { i - s } -> same_as<iter_difference_t<I>>; };2 Let
ibe an iterator of typeI, andsa sentinel of typeSsuch that[i, s)denotes a range. LetNbe the smallest number of applications of++inecessary to makebool(i == s)betrue.SandImodelsized_sentinel_for<S, I>only if- [2.1]{.pnum} If `N` is representable by `iter_difference_t<I>`, then `s - i` is well-defined and equals `N`. - [2.2]{.pnum} If `−N` is representable by `iter_difference_t<I>`, then `i - s` is well-defined and equals `−N`.3 Remarks: Pursuant to [namespace.std], users may specialize
disable_sized_sentinelenable_sized_sentinel_forfor cv-unqualified non-array object typesSandIifSand/orIis a program-defined type. Such specializations shall be usable in constant expressions ([expr.const]) and have typeconst bool.4 [ Note:
disable_sized_sentinelenable_sized_sentinel_forallows use of sentinels and iterators with the library that satisfy but do not in fact modelsized_sentinel_for. — end note ]
Change 24.2 [ranges.syn]:
Change 24.3.9 [range.prim.size]:
1 The name
sizedenotes a customization point object. The expressionranges::size(E)for some subexpressionEwith typeTis expression-equivalent to:
Change 24.4.3 [range.sized]:
1 The
sized_rangeconcept specifies the requirements of arangetype that knows its size in constant time with thesizefunction.
and
4 Remarks: Pursuant to [namespace.std], users may specialize
disable_sized_rangeenable_sized_rangefor cv-unqualified program-defined types. Such specializations shall be usable in constant expressions ([expr.const]) and have typeconst bool.5 [ Note:
disable_sized_rangeenable_sized_rangeallows use of range types with the library that satisfy but do not in fact modelsized_range. — end note ]