I’d like to propose some minor extensions to enable functionality resembling C++20 ranges. We already have a bunch of the heavy coding done in STLExtras.h, but we are missing some features which make ranges a little more accessible and consistent with C++20.
It seems all the current range-like code generates common_ranges (i.e. ranges with the same iterator type representing both begin+end). We could continue this practice into new infrastructure to reduce complexity + compile-time and maximize compatibility with existing code.
I think it makes sense to adopt an API which mirrors C++20 ranges. That is, view adapters are expressed as constexpr inline variables with call + pipe operator overloads (likely in a new namespace to avoid name collisions… call it llvm::views). View adapters implement both functional (views::reverse(A)) and a pipe syntax (A | views::reverse). The pipe syntax enables more ergonomic code when closures get complex.
Example:
auto R = reverse(drop(take(Vec, 2), 3));
vs
auto R = Vec | take(2) | drop(3) | reverse;
Additionally, users should be able to store a series of pipe-syntax adapters to a named variable or return the collection of adapters from a function:
auto AdapterSeq = take(2) | drop(3) | reverse;
auto R = Vec | AdapterSeq;
If you’d like to have an idea of the complexity of the code involved, I created a Phabricator review which implements the pipe syntax described above. It uses a single template class (well two specializations of one class) to implement all the view adapters, which makes adding new view adapters very simple once you have your custom iterator / range implemented. It also implements for named variable storage of an adapter sequence (although I realize now that I could use a std::tuple and a fold expression to simplify this code somewhat). This does not modify llvm::iterator_range as described above since I would like community approval before moving forward with that implementation.
The following are the steps in the patch series as I see them:
Bug fix(es) in existing range code:
- Iterator types which carry a callable object are not currently copy-assignable (
llvm::mapped_iterator,llvm::filter_iterator, etc.).- Example compile failure.
- See this explanation as to why the ranges library uses
std::optionalto encapsulate the callable object. - Can easily make these iterators default constructible with
std::optionalencapsulation. - No need to use
std::optionalifstd::is_function_vis true… just store as a pointer (orstd::reference_wrapperif default construction is not a priority.
- Audit existing eager evaluation to ensure behavior matches C++20 spec.
-
For example,
llvm::drop_begin()is evaluated eagerly whilestd::ranges::views::dropis evaluated lazily.
-
For example,
- Use
llvm::adl_begin()andllvm::adl_end()when forming ranges instead of member functions.- Enables use of range code on C-arrays.
-
Example compile failure using
llvm::map_range().
Create a place to house concept-like code.
- Particularly any utilities used to determine whether a type is a forward iterable range and iterator types associated with a container-like type.
Proposed llvm::iterator_range-related updates:
- Create select different base class / implementation layer depending on category of the iterator type.
- For example, a iterator of category
std::forward_iterator_tagselects a base class ofllvm::forward_range. - More member functions are layered into the selected base class as the category advances (
operator[]for random-access, etc).
- For example, a iterator of category
- Add an untemplated tag class as the base class for all
iterator_rangeclasses.- Helps to identify any class derived from
iterator_rangeor one of its bases in SFINAE.
- Helps to identify any class derived from
Implement pipe syntax:
- Define the view adapter implementation template.
- Adopt new view adapter implementation for existing range adapter functions.
- Possibly define adapters in a new namespace to allow correlation between LLVM view adapter names and C++20 names (i.e.
llvm::views::transforminstead ofllvm::map_range).
- Possibly define adapters in a new namespace to allow correlation between LLVM view adapter names and C++20 names (i.e.
- Define the adapter sequence template to enable storage of adapters to a named variable.
- Example:
auto Adaters = reverse | elements<2> | drop_while([ ](auto X){ return X<10; });
Implement to() function from C++23 spec (at least overloads which create a container).
- Enable simple copy of range elements to a specified container type.
- Example:
std::list<char> L{ 'a', 'B', 'c', 'D', 'e', 'F };
auto IsLower = [](char X){ return X > 'a' && X <= 'z'; };
auto Str = L | filter(IsLower) | transform(::toupper) | to<SmallString<10>>();
Benefits of more C++20-like ranges infrastructure:
- More useful ranges with additional category-specific member functions.
- Parity with C++20 spec makes eventual C++20 adoption simpler.
- Potential to write more ergonomic + efficient code.
Concerns mentioned in Phabricator
- Compile-time with range-heavy code. Might make it easier for devs to inflate build times.