Doc. no.: | P3060R0 |
Date: | 2023-11-22 |
Audience: | LEWG |
Reply-to: | Weile Wei <weilewei09@gmail.com> |
Abstract
This proposal suggests adding std::ranges::upto(n)
to the C++ Standard Library as a range adaptor that generates a sequence of integers from 0
to n-1
.
Motivation
Typically, generating such a sequence of integers involves std::views::iota
combined with std::views::take
, or manual iteration, which can be cumbersome and not straightforward, particularly with unsigned types like container sizes. Consider the following typical usage:
before
|
for (int i : std::views::iota(0) | std::views::take(10)) {
std::cout << i << ' ';
}
std::cout << '\n';
|
---|
after
|
for (int i : std::views::upto(10)) {
std::cout << i << ' ';
}
std::cout << '\n';
|
std::ranges::upto(n)
eases this pattern by providing a straightforward and type-safe method to generate integer sequences, improving code readability and minimizing repetitive code.
Implementation
namespace std::ranges {
inline constexpr auto upto = [] <std::integral I> (I n) {
return std::views::iota(I{}, n);
};
}
Test case:
int main() {
constexpr auto up_to_ten = std::ranges::upto(10);
for (auto i : up_to_ten) {
std::cout << i << ' ';
}
std::cout << std::endl;
return 0;
}
Expected output:
0 1 2 3 4 5 6 7 8 9
The implementation is confirmed to work with clang version 16.0.0+, gcc version 11.1+, and msvc v19.32+: T7YoMzsdG
Background
Two preceding proposals have provided fundation for std::ranges::upto(n)
:
- P2214R2: A Plan for C++26 Ranges highlights the issue with
views::iota(0, r.size())
not compiling due to mismatched types. std::ranges::views::iota
requires both arguments to be of the same type, or at least commonly comparable. This becomes problematic when comparing int
(often 32-bit) with std::size_t
(often 64-bit), which is usually wider on 64-bit systems. An example illustrating this issue is available at TGzP4be7K.
std::vector vec(10, 0);
auto res = std::ranges::views::iota(0, vec.size());
- P1894R0: Proposal of std::upto, std::indices and std::enumerate proposed an implementation (see below) that our proposal refines. Our approach offers a more intuitive and consistent interface, fitting seamlessly with existing standard library components without adding complexity.
namespace std {
template<typename Int>
constexpr auto upto(Int&& i) noexcept {
return std::ranges::iota_view{Int(),std::forward<Int>(i)};
}
}
Technical Decisions
- Limiting to
std::integral
: This constraint ensurenality is restricted to integral types, yielding predictable behavior and avoiding the pitfalls of generic types. A more relaxed constraint to std::default_initializable
and std::incrementable
would allow iterators to compile but might introduce undefined behavior. An example illustrating this issue is available at hGx1T9sxG:
namespace std::ranges {
inline constexpr auto upto2 = []
<typename T>
requires std::default_initializable<T> && std::incrementable<T>
(T n) {
return std::views::iota(T{}, n);
};
}
int main() {
int myint = 10;
int* ptr = &myint;
auto up_to_ten2 = std::ranges::upto2(CustomIterator{ptr});
for (auto i : up_to_ten2) {
std::cout << *i << ' ';
}
return 0;
}
-
Lambda-based Approach: Employing a lambda is consistent with the established range adaptor patterns. Moreoever, the constexpr
specifier on the lambda ensures that the lambda object is a constant expression.
-
Leveraging Existing iota_view
Instead of Creating a New upto_view
: Introducing upto_view
would mean adding a component similar to what already exists, causing confusion and maintainability issues for users. By leveraging iota_view
, we simplify the implementation and reuse of what the current C++ Standard Library offers. Additionally, any future optimizations to iota_view
will automatically benefit std::ranges::upto
. Therefore, by extending iota_view
, std::ranges::upto
becomes more maintainable, efficient, and simple.
Wording
This wording is relative to N4964.
Insert a new item in the std::ranges
namespace:
namespace std::ranges {
inline constexpr auto upto = [] <std::integral I> (I n) {
return std::views::iota(I{}, n);
};
}
Add a new subsection to describe std::ranges::upto
:
26.6.4.2.1 upto function [range.upto]
-1- The name upto
denotes a range adaptor object ([range.adaptor.object]). The expression std::ranges::upto(n)
is expression-equivalent to std::views::iota(I{}, n)
where I
is the type of n
.
-2- Constraints: The type I
shall satisfy the std::integral
concept.
-3- Returns: A view of the integers in the range [0, n).
-4- Complexity: Constant time.
-5- Remarks: This function template shall not participate in overload resolution unless std::integral<I>
is true.
References
Add std::ranges::upto(n)
Abstract
This proposal suggests adding
std::ranges::upto(n)
to the C++ Standard Library as a range adaptor that generates a sequence of integers from0
ton-1
.Motivation
Typically, generating such a sequence of integers involves
std::views::iota
combined withstd::views::take
, or manual iteration, which can be cumbersome and not straightforward, particularly with unsigned types like container sizes. Consider the following typical usage:before
after
std::ranges::upto(n)
eases this pattern by providing a straightforward and type-safe method to generate integer sequences, improving code readability and minimizing repetitive code.Implementation
Test cases verified on various platforms
Test case:
Expected output:
The implementation is confirmed to work with clang version 16.0.0+, gcc version 11.1+, and msvc v19.32+: T7YoMzsdG
Background
Two preceding proposals have provided fundation for
std::ranges::upto(n)
:views::iota(0, r.size())
not compiling due to mismatched types.std::ranges::views::iota
requires both arguments to be of the same type, or at least commonly comparable. This becomes problematic when comparingint
(often 32-bit) withstd::size_t
(often 64-bit), which is usually wider on 64-bit systems. An example illustrating this issue is available at TGzP4be7K.Technical Decisions
std::integral
: This constraint ensures functionality is restricted to integral types, yielding predictable behavior and avoiding the pitfalls of generic types. A more relaxed constraint tostd::default_initializable
andstd::incrementable
would allow iterators to compile but might introduce undefined behavior. An example illustrating this issue is available at hGx1T9sxG:Lambda-based Approach: Employing a lambda is consistent with the established range adaptor patterns. Moreoever, the
constexpr
specifier on the lambda ensures that the lambda object is a constant expression.Leveraging Existing
iota_view
Instead of Creating a Newupto_view
: Introducingupto_view
would mean adding a component similar to what already exists, causing confusion and maintainability issues for users. By leveragingiota_view
, we simplify the implementation and reuse of what the current C++ Standard Library offers. Additionally, any future optimizations toiota_view
will automatically benefitstd::ranges::upto
. Therefore, by extendingiota_view
,std::ranges::upto
becomes more maintainable, efficient, and simple.Wording
This wording is relative to N4964.
Header <ranges> synopsis [ranges.syn]
Insert a new item in the
std::ranges
namespace:New subsection under [range.iota.view]
Add a new subsection to describe
std::ranges::upto
:26.6.4.2.1 upto function [range.upto]
-1- The name
upto
denotes a range adaptor object ([range.adaptor.object]). The expressionstd::ranges::upto(n)
is expression-equivalent tostd::views::iota(I{}, n)
whereI
is the type ofn
.-2- Constraints: The type
I
shall satisfy thestd::integral
concept.-3- Returns: A view of the integers in the range [0, n).
-4- Complexity: Constant time.
-5- Remarks: This function template shall not participate in overload resolution unless
std::integral<I>
is true.References
P2214R2 A Plan for C++26 Ranges. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r0.html ↩︎
P1894R0 Proposal of std::upto, std::indices and std::enumerate. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1894r0.pdf ↩︎