Doc. no.: | P3060R1 |
Date: | 2024-2-14 |
Audience: | LEWG |
Reply-to: |
Weile Wei <weilewei09@gmail.com>
Zhihao Yuan <zy@miator.net> |
Add std::views::upto(n)
Revision History
- Since R0
-
- Move
upto
from the std::ranges
namespace into std::views
- Use a more convincing example
- Incorporate with the current
iota
wording in the standard
Abstract
We propose adding std::views::upto(n)
to the C++ Standard Library as a range adaptor that generates a sequence of integers from 0
to n-1
.
Motivation
Currently, iota(0, ranges::size(rng))
does not compile due to mismatched types (see point 1 in Background section). Then, users need to write a workaround like iota(range_size_t<decltype(rng)>{}, ranges::size(rng))
, which is not straightforward and cumbersome. An example illustrating this issue is available at x8nWxqE9v:
C++23
|
std::vector rng(5, 0);
auto res2 = iota(range_size_t<decltype(rng)>{}, ranges::size(rng));
std::print("{}", res2);
|
---|
P3060
|
std::vector rng(5, 0);
std::print("{}", views::upto(ranges::size(rng)));
|
std::views::upto(n)
eases this pattern by providing a straightforward method to generate integer sequences, improving readability and killing arcane code.
Implementation and Usage
Implementation details:
namespace std::views {
inline constexpr auto upto = [] <std::integral I> (I n) {
return std::views::iota(I{}, n);
};
}
Usage:
int main() {
std::vector rng(5, 0);
std::print("{}", views::upto(ranges::size(rng)));
}
The implementation is confirmed to work with clang version 16.0.0+, gcc version 11.1+, and msvc v19.32+: 4MKh317dr
Two preceding proposals have provided fundation for std::views::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. The same example from above Motivation section can be viewed at x8nWxqE9v.
std::vector rng(5, 0);
auto res1 = iota(0, ranges::size(rng));
-
P1894R0: Proposal of std::upto, std::indices and std::enumerate proposed an implementation (see below) that our proposal refines. Our approach offers a more consistent interface, fitting seamlessly with existing standard library.
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 ensures functionality 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 a3neb43a4:
namespace std::views {
inline constexpr auto upto2 =
[]<typename T> (T n)
requires std::default_initializable<T> && std::incrementable<T>
{
return std::views::iota(T{}, n);
};
}
int main() {
int myint = 5;
int* ptr = &myint;
auto up_to_five = std::views::upto2(CustomIterator{ptr});
for (auto i : up_to_five) {
std::cout << *i << ' ';
}
return 0;
}
-
Lambda-based Approach: Using a lambda is consistent with the established range adaptor patterns. Moreover, the use of constexpr
allows for the evaluation to occur at compile-time.
-
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::views::upto
. Therefore, by extending iota_view
, std::views::upto
becomes more maintainable, efficient, and simple.
Wording
Add a new parapgrah under [range.iota.overview]:
26.6.4.1 Overview [range.iota.overview]
The name views::upto
denotes a customization point object ([customization.point.object]). Let E
be an expression and let T
be remove_cvref_t<decltype((E))>
. If T
does not model integral
, then views::upto(E)
is ill-formed. Otherwise, the expression views::upto(E)
is expression-equivalent to views::iota(T(), E)
.
References
Zhihao Yuan <zy@miator.net>
Add std::views::upto(n)
Revision History
upto
from thestd::ranges
namespace intostd::views
iota
wording in the standardAbstract
We propose adding
std::views::upto(n)
to the C++ Standard Library as a range adaptor that generates a sequence of integers from0
ton-1
.Motivation
Currently,
iota(0, ranges::size(rng))
does not compile due to mismatched types (see point 1 in Background section). Then, users need to write a workaround likeiota(range_size_t<decltype(rng)>{}, ranges::size(rng))
, which is not straightforward and cumbersome. An example illustrating this issue is available at x8nWxqE9v:C++23
P3060
std::views::upto(n)
eases this pattern by providing a straightforward method to generate integer sequences, improving readability and killing arcane code.Implementation and Usage
Implementation details:
Usage:
The implementation is confirmed to work with clang version 16.0.0+, gcc version 11.1+, and msvc v19.32+: 4MKh317dr
Background
Two preceding proposals have provided fundation for
std::views::upto(n)
:P2214R2: A Plan for C++26 Ranges[1] 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 comparingint
(often 32-bit) withstd::size_t
(often 64-bit), which is usually wider on 64-bit systems. The same example from above Motivation section can be viewed at x8nWxqE9v.P1894R0: Proposal of std::upto, std::indices and std::enumerate[2] proposed an implementation (see below) that our proposal refines. Our approach offers a more consistent interface, fitting seamlessly with existing standard library.
Technical Decisions
Limiting to
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 a3neb43a4:Lambda-based Approach: Using a lambda is consistent with the established range adaptor patterns. Moreover, the use of
constexpr
allows for the evaluation to occur at compile-time.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::views::upto
. Therefore, by extendingiota_view
,std::views::upto
becomes more maintainable, efficient, and simple.Wording
Add a new parapgrah under [range.iota.overview]:
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 ↩︎