| Document #: | P2393R1 |
| Date: | 2021-08-06 |
| Project: | Programming Language C++ |
| Audience: |
LWG |
| Reply-to: |
Tim Song <t.canens.cpp@gmail.com> |
This paper revamps the specification and use of integer-class types to resolve a number of issues, including [LWG3366], [LWG3376], and [LWG3575].
Integer-class types, introduced in [P1522R1], are implementation-defined class types that are supposed to “behave as integers do”. Unfortunately, they are not yet required to do that, and the failure to do so leads to a number of issues in the library specification. For example:
common_type, which causes issues in various ranges components such as zip_view ([P2321R2]) and join_view (24.7.12.3 [range.join.iterator]) that uses common_type_t to determine the difference type of a range resulting from the composition of multiple ranges.ranges::equal) that may need to compare distances obtained from two ranges can even be implemented.Additionally, there’s a pervasive problem in the ranges and algorithms clauses in that the wording fails to take into account the fact that random access iterator operations are only required to work with the iterator’s difference type, and not any other integer-like type. This was not a major issue in practice before C++20 because reasonable users (and even somewhat less reasonable ones) do not normally go out of the way to define deleted overloads for other integer types (or constrain their overload to require exactly the same type), but now that integer-class types can require explicit conversions, things are more problematic.
First, tighten the specification of integer-class types to mandate support for what algorithms and range adaptors require, and clean up several library issues in the process.
common_type_t to compute the difference type of a range. (Only the signed case is considered because a) there is no corresponding use case for unsigned and b) the property doesn’t hold for some built-in unsigned integer types that could be promoted to a signed type by integral promotion.)Next, clean up the ranges wording (again) to explicitly cast to the difference type where required.
Finally, add blanket wording in the algorithms clause that i + n and i - n for iterator i and integer-like type n in the wording behave as-if n is first cast to i’s difference type. This allows the spec to have things like first1 + (last2 - first2) without worrying about having to cast them (a fully correct implementation would still need to cast, however).
This wording is relative to [N4892].
[ Drafting note: Because integer-class type is only used in this subclause (the rest of the standard uses integer-like), I did not rename it. Cf.
enum class. ][ Editor's note: [P2321R2] § 5.4 also contains edits to this subclause. The wording below subsumes those edits and should be applied instead if both papers are adopted. ]
2 A type
Iis an integer-class type if it is in a set of implementation-defined class types that behave as integer types do, as defined below. [ Note ?: An integer-class type is not necessarily a class type. — end note ]3 The range of representable values of an integer-class type is the continuous set of values over which it is defined. The values 0 and 1 are part of the range of every integer-class type. If any negative numbers are part of the range, the type is a signed-integer-class type; otherwise, it is an unsigned-integer-class type. For any integer-class type, its range of representable values is either -2N-1 to 2N-1-1 (inclusive) for some integer N, in which case it is a signed-integer-class type, or 0 to 2N-1 (inclusive) for some integer N, in which case it is an unsigned-integer-class type. In both cases, N is called the width of the integer-class type. The width of an integer-class type is greater than that of every integral type of the same signedness.
[ Editor's note: Move paragraph 11 here to make integer-like available for use in subsequent wording. ]
11 A type
Iother than cvboolis integer-like if it modelsintegral<I>or if it is an integer-class type. An integer-like type I is signed-integer-like if it modelssigned_integral<I>or if it is a signed-integer-class type. An integer-like typeIis unsigned-integer-like if it modelsunsigned_integral<I>or if it is an unsigned-integer-class type.[ Drafting note: The unique addition ensures that even if an implementation decides to provide two integer-class types of the same signedness and width, the result of binary operators are still well-defined. ]
4 For every integer-class type
I, letB(I)be a unique hypothetical extended integer type of the same signedness with the same width (6.8.2 [basic.fundamental]) asIthe smallest width (6.8.2 [basic.fundamental]) capable of representing the same range of values. The width ofIis equal to the width ofB(I). [ Note ?: The corresponding hypothetical specializationnumeric_limits<B(I)>meets the requirements onnumeric_limitsspecializations for integral types (17.3.5 [numeric.limits]). — end note ] For every integral typeJ, letB(J)be the same type asJ.[ Editor's note: Reorder paragraph 6 before 5. ]
6 Expressions of integer-class type are explicitly convertible to any integer-likeintegral type, and implicitly convertible to any integer-class type of equal or greater width and the same signedness. Expressions of integral type are both implicitly and explicitly convertible to any integer-class type. Conversions between integral and integer-class types and between two integer-class types do not exit via an exception. The result of such a conversion is the unique value of the destination type that is congruent to the source modulo 2N, where N is the width of the destination type.
5 Let
aandbbe an objectobjects of integer-class typeI, letbbe an object of integer-like typeI2such that the expressionbis implicitly convertible toI, letxandybe respectively objects of typeB(I)andB(I2)as described above that represent the same values asaandbrespectively, and letcbe an lvalue of any integral type.
- (5.?) The expressions
a++anda--shall be prvalues of typeIwhose values are equal to that ofaprior to the evaluation of the expressions. The expressiona++shall modify the value ofaby adding1to it. The expressiona--shall modify the value ofaby subtracting1from it.- (5.?) The expressions
++a,--a, and&ashall be expression-equivalent toa += 1,a -= 1, andaddressof(a), respectively.[ Drafting note: The grammar term unary-operator includes
* & + - ! ~. ]
- (5.1) For every unary-operatorunary operator
@other than&for which the expression@xis well-formed,@ashall also be well-formed and have the same value, effects, and value category as@xprovided that value is representable byI. If@xhas typebool, so too does@a; if@xhas typeB(I), then@ahas typeI.- (5.2) For every assignment operator
@=for whichc @= xis well-formed,c @= ashall also be well-formed and shall have the same value and effects asc @= x. The expressionc @= ashall be an lvalue referring toc.- (5.?) For every assignment operator
@=for whichx @= yis well-formed,a @= bshall also be well-formed and shall have the same effects asx @= y, except that the value that would be stored intoxis stored intoa. The expressiona @= bshall be an lvalue referring toa.- (5.3) For every non-assignment binary operator
@for whichx @ yandy @ xareis well-formed,a @ bandb @ ashall also be well-formed and shall have the same value, effects, and value category asx @ yandy @ xrespectivelyprovided that value is representable byI. Ifx @ yhas typebool, so too doesa @ b; ifx @ yory @ xhas typeB(I), thena @ borb @ a, respectively, has typeI; ifx @ yory @ xhas typeB(I2), thena @ borb @ a, respectively, has typeI2; ifx @ yory @ xhas any other type, thena @ borb @ a, respectively, has that type.7 An expression
Eof integer-class typeIis contextually convertible toboolas if bybool(E != I(0)).8 All integer-class types model
regular(18.6 [concepts.object]) andthree_way_comparable<strong_ordering>(17.11.4 [cmp.concept])totally_ordered(18.5.4 [concept.totallyordered]).9 A value-initialized object of integer-class type has value 0.
[ Drafting note: There are some issues with the
numeric_limitspecialization:digitsis defined in terms ofradix, so it doesn’t make sense to define the former but not the latter, and the definition ofdigitsis also incorrect for signed types. Instead of trying to fix this piecemeal and maintain an ever-growing list, we can simply specify this in terms ofB(I). ]10 For every (possibly cv-qualified) integer-class type
I,numeric_limits<I>is specialized such that each static data membermhas the same value asnumeric_limits<B(I)>::m, and each static member functionfreturnsI(numeric_limits<B(I)>::f()).:
- (10.1)
numeric_limits<I>::is_specializedistrue,- (10.2)
numeric_limits<I>::is_signedistrueif and only ifIis a signed-integer-class type,- (10.3)
numeric_limits<I>::is_integeristrue,- (10.4)
numeric_limits<I>::is_exactistrue,- (10.5)
numeric_limits<I>::digitsis equal to the width of the integer-class type,- (10.6)
numeric_limits<I>::digits10is equal tostatic_cast<int>(digits * log10(2)), and- (10.7)
numeric_limits<I>::min()andnumeric_limits<I>::max()return the lowest and highest representable values ofI, respectively, andnumeric_limits<I>::lowest()returnsnumeric_limits<I>::min().? For any two integer-like types
I1andI2, at least one of which is an integer-class type,common_type_t<I1, I2>denotes an integer-class type whose width is not less than that ofI1orI2. If bothI1andI2are signed-integer-like types, thencommon_type_t<I1, I2>is also a signed-integer-like type.12
is-integer-like<I>is true if and only if I is an integer-like type.is-signed-integer-like<I>is true if and only if I is a signed-integer-like type.
template<not-same-as<subrange> R> requires borrowed_range<R> && convertible-to-non-slicing<iterator_t<R>, I> && convertible_to<sentinel_t<R>, S> constexpr subrange(R&& r) requires (!StoreSize || sized_range<R>);6 Effects: Equivalent to:
2 The name
views::takedenotes a range adaptor object (24.7.2 [range.adaptor.object]). LetEandFbe expressions, letTberemove_cvref_t<decltype((E))>, and letDberange_difference_t<decltype((E))>. Ifdecltype((F))does not modelconvertible_to<D>,views::take(E, F)is ill-formed. Otherwise, the expressionviews::take(E, F)is expression-equivalent to:
(2.1) If T is a specialization of
ranges::empty_view(24.6.2.2 [range.empty.view]), then((void) F, decay-copy(E)), except that the evaluations ofEandFare indeterminately sequenced.(2.2) Otherwise, if
Tmodelsrandom_access_rangeandsized_rangeand is
- (2.2.1) […]
then
T(ranges::begin(E), ranges::begin(E) + std::min<D>(ranges::distancesize(E), F)), except thatEis evaluated only once.(2.3) Otherwise,
ranges::take_view(E, F).
namespace std::ranges { template<view V> class take_view : public view_interface<take_view<V>> { // [...] constexpr auto begin() requires (!simple-view<V>) { if constexpr (sized_range<V>) { if constexpr (random_access_range<V>) return ranges::begin(base_); else { auto sz = range_difference_t<V>(size()); return counted_iterator((ranges::begin(base_), sz); } } else return counted_iterator(ranges::begin(base_), count_); } constexpr auto begin() const requires range<const V> { if constexpr (sized_range<const V>) { if constexpr (random_access_range<const V>) return ranges::begin(base_); else { auto sz = range_difference_t<const V>(size()); return counted_iterator(ranges::begin(base_), sz); } } else return counted_iterator(ranges::begin(base_), count_); } constexpr auto end() requires (!simple-view<V>) { if constexpr (sized_range<V>) { if constexpr (random_access_range<V>) return ranges::begin(base_) + range_difference_t<V>(size()); else return default_sentinel; } else return sentinel<false>{ranges::end(base_)}; } constexpr auto end() const requires range<const V> { if constexpr (sized_range<const V>) { if constexpr (random_access_range<const V>) return ranges::begin(base_) + range_difference_t<const V>(size()); else return default_sentinel; } else return sentinel<true>{ranges::end(base_)}; } // [...] }; template<class R> take_view(R&&, range_difference_t<R>) -> take_view<views::all_t<R>>; }
2 The name
views::dropdenotes a range adaptor object (24.7.2 [range.adaptor.object]). LetEandFbe expressions, letTberemove_cvref_t<decltype((E))>, and letDberange_difference_t<decltype((E))>. Ifdecltype((F))does not modelconvertible_to<D>,views::drop(E, F)is ill-formed. Otherwise, the expressionviews::drop(E, F)is expression-equivalent to:
(2.1) If T is a specialization of
ranges::empty_view(24.6.2.2 [range.empty.view]), then((void) F, decay-copy(E)), except that the evaluations ofEandFare indeterminately sequenced.(2.2) Otherwise, if
Tmodelsrandom_access_rangeandsized_rangeand is
- (2.2.1) […]
then
T(ranges::begin(E) + std::min<D>(ranges::distancesize(E), F), ranges::end(E)), except thatEis evaluated only once.(2.3) Otherwise,
ranges::drop_view(E, F).
2 The name
views::counteddenotes a customization point object (16.3.3.3.6 [customization.point.object]). LetEandFbe expressions, letTbedecay_t<decltype((E))>, and letDbeiter_difference_t<T>. Ifdecltype((F))does not modelconvertible_to<D>,views::counted(E, F)is ill-formed.[ Note 1: This case can result in substitution failure when
views::counted(E, F)appears in the immediate context of a template instantiation. — end note ]Otherwise,
views::counted(E, F)is expression-equivalent to:
- (2.1) If
Tmodelscontiguous_iterator, thenspan(to_address(E), static_cast<size_t>(static_cast<D>(F))).- (2.2) Otherwise, if
Tmodelsrandom_access_iterator, thensubrange(E, E + static_cast<D>(F)), except thatEis evaluated only once.- (2.3) Otherwise,
subrange(counted_iterator(E, F), default_sentinel).
? In the description of the algorithms, given an iterator
awhose difference type isD, and an expressionnof integer-like type other thancv D, the semantics ofa + nanda - nare, respectively, those ofa + D(n)anda - D(n).
[LWG3366] Casey Carter. Narrowing conversions between integer and integer-class types.
https://wg21.link/lwg3366
[LWG3376] Jonathan Wakely. “integer-like class type” is too restrictive.
https://wg21.link/lwg3376
[LWG3575] Jiang An. <=> for integer-class types isn’t consistently specified.
https://wg21.link/lwg3575
[N4892] Thomas Köppe. 2021-06-18. Working Draft, Standard for Programming Language C++.
https://wg21.link/n4892
[P1522R1] Eric Niebler. 2019-07-28. Iterator Difference Type and Integer Overflow.
https://wg21.link/p1522r1
[P2321R2] Tim Song. 2021-06-11. zip.
https://wg21.link/p2321r2