Skip to content

Commit d252cea

Browse files
authored
Initial range operators implementation (#1172)
* Initial range operators implementation Examples: `v.begin()...end()` and `1..=10` * Add some docs for `...` and `..=` (and `..` as catchup)
1 parent 15f9be1 commit d252cea

17 files changed

+328
-27
lines changed

docs/cpp2/common.md

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Cpp2 supports using Cpp1 user-defined literals for compatibility, to support sea
168168

169169
Both **`123.nm()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general.
170170

171+
171172
## <a id="operators"></a> Operators
172173

173174
Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2.
@@ -188,7 +189,7 @@ if !vec.empty() {
188189
| `+` | `#!cpp +100` | `#!cpp +100` |
189190
| `-` | `#!cpp -100` | `#!cpp -100` |
190191

191-
The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example:
192+
The operators `.`, `..`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, `...`, `..=`, and `$` are postfix. For example:
192193

193194
``` cpp title="Using postfix operators"
194195
// Cpp1 examples, from cppfront's own source code:
@@ -201,7 +202,7 @@ The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. F
201202

202203
Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators).
203204

204-
> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS).
205+
> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. The function call syntax `x..f()` calls a type-scope function only. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS).
205206
206207
| Unary operator | Cpp2 example | Cpp1 equivalent |
207208
|---|---|---|
@@ -213,18 +214,13 @@ Postfix notation lets the code read fluidly left-to-right, in the same order in
213214
| `#!cpp --` | `#!cpp iter--` | `#!cpp --iter` |
214215
| `(` `)` | `#!cpp f( 1, 2, 3)` | `#!cpp f( 1, 2, 3)` |
215216
| `[` `]` | `#!cpp vec[123]` | `#!cpp vec[123]` |
216-
| `$` | `val$` | _reflection — no Cpp1 equivalent yet_ |
217-
218-
> Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`.
217+
| `...` (half-open range operator) | `#!cpp v.begin()...v.end()` | `#!cpp std::ranges::subrange(v.begin(), v.end())` |
218+
| `..=` (closed range operator) | `#!cpp 1..=10` | `#!cpp std::views::iota(1, 11)` |
219+
| `$` (capture operator) | `val$` | _reflection — no Cpp1 equivalent yet_ |
219220

220-
Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~` are used as binary operators they must be preceded by whitespace. For example:
221-
222-
| Unary postfix operators that<br>are also binary operators | Cpp2 example | Cpp1 equivalent |
223-
|---|---|---|
224-
| `#!cpp *` | `#!cpp pobj* * 42` | `#!cpp (*pobj)*42` |
225-
| `#!cpp &` | `#!cpp obj& & mask` <p> (note: allowed in unsafe code only) | `#!cpp &obj & mask` |
221+
> Note: The `...` pack expansion syntax is also supported. The above `...` and `..=` are the Cpp2 range operators, which overlap in syntax.
226222
227-
For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators).
223+
> Note: Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`.
228224
229225

230226
### <a id="binary-operators"></a> Binary operators

docs/cpp2/expressions.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# Common expressions
33

4-
## <a id="ufcs"></a> Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax
4+
## <a id="ufcs"></a> Calling functions: `f(x)` syntax, `x.f()` UFCS syntax, and `x..f()` members-only syntax
55

66
A function argument list is a [list](common.md#lists) of arguments enclosed by `(` `)` parentheses.
77

@@ -11,6 +11,8 @@ A function call like `x.f()` is a unified function call syntax (aka UFCS) call.
1111

1212
An operator notation call like `#!cpp a + b` will call an overloaded operator function if one is available, as usual in C++.
1313

14+
A function call like `x..f()` will consider only member functions.
15+
1416
For example:
1517

1618
``` cpp title="Function calls" hl_lines="3 7 11 16 19 20"
@@ -221,6 +223,41 @@ test(42);
221223
For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`.
222224
223225
226+
## <a id="ranges"></a> `...` and `..=` — range operators
227+
228+
`...` and `..=` designate a range of things. In addition to using `...` for variadic parameters, variadic pack expansion, and fold expressions as in Cpp1, Cpp2 also supports using `begin...end` for a half-open range (that does not include `end`) and `first..=last` for a closed range (that does include `last`).
229+
230+
For example:
231+
232+
``` cpp title="Using ... and ..= for ranges" hl_lines="4,11"
233+
test: (v: std::vector<std::string>) =
234+
{
235+
// Print strings from "Nonesuch" (if present) onward
236+
i1 := v.std::ranges::find("Nonesuch");
237+
for i1 ... v.end() do (e) {
238+
std::cout << " (e*)$\n";
239+
}
240+
241+
if v.ssize() > 2 {
242+
// Print indexes 1 and 2 of v
243+
for 1 ..= 2 do (e) {
244+
std::cout << " (e)$ (v[e])$\n";
245+
}
246+
}
247+
}
248+
249+
main: () = {
250+
vec: std::vector<std::string> = ("Beholder", "Grue", "Nonesuch", "Wumpus");
251+
test( vec );
252+
}
253+
// Prints:
254+
// Nonesuch
255+
// Wumpus
256+
// 1 Grue
257+
// 2 Nonesuch
258+
```
259+
260+
224261
## <a id="captures"></a> `$` — captures, including interpolations
225262

226263
Suffix `$` is pronounced **"paste the value of"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending on the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression.

include/cpp2util.h

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,12 +2200,15 @@ constexpr auto unsafe_narrow( X&& x ) noexcept -> decltype(auto)
22002200
//
22012201
// Does not perform any dynamic memory allocation - each string_view
22022202
// is directly bound to the string provided by the host environment
2203+
//
2204+
// Note: These string_views happen to be null-terminated. We ought
2205+
// to also have a std::zstring_view to express that...
22032206
//
22042207
//-----------------------------------------------------------------------
22052208
//
2206-
struct args_t
2209+
struct args
22072210
{
2208-
args_t(int c, char** v) : argc{c}, argv{v} {}
2211+
args(int c, char** v) : argc{c}, argv{v} {}
22092212

22102213
class iterator {
22112214
public:
@@ -2238,24 +2241,93 @@ struct args_t
22382241
auto end() const -> iterator { return iterator{ argc, argv, argc }; }
22392242
auto cbegin() const -> iterator { return begin(); }
22402243
auto cend() const -> iterator { return end(); }
2241-
auto size() const -> std::size_t { return cpp2::unsafe_narrow<std::size_t>(argc); }
2244+
auto size() const -> std::size_t { return cpp2::unsafe_narrow<std::size_t>(ssize()); }
22422245
auto ssize() const -> int { return argc; }
22432246

22442247
auto operator[](int i) const {
2245-
if (0 <= i && i < argc) { return std::string_view{ argv[i] }; }
2246-
else { return std::string_view{}; }
2248+
if (0 <= i && i < ssize()) { return std::string_view{ argv[i] }; }
2249+
else { return std::string_view{}; }
22472250
}
22482251

22492252
mutable int argc = 0; // mutable for compatibility with frameworks that take 'int& argc'
22502253
char** argv = nullptr;
22512254
};
22522255

2253-
inline auto make_args(int argc, char** argv) -> args_t
2256+
inline auto make_args(int argc, char** argv) -> args
22542257
{
2255-
return args_t{argc, argv};
2258+
return args{argc, argv};
22562259
}
22572260

22582261

2262+
//-----------------------------------------------------------------------
2263+
//
2264+
// range: a range of [begin, end) or [first, last]
2265+
//
2266+
//-----------------------------------------------------------------------
2267+
//
2268+
template<typename T>
2269+
struct range
2270+
{
2271+
range(
2272+
T const& f,
2273+
T const& l,
2274+
bool include_last = false
2275+
)
2276+
: first{ f }
2277+
, last{ l }
2278+
{
2279+
if (include_last) {
2280+
++last;
2281+
}
2282+
}
2283+
2284+
class iterator {
2285+
public:
2286+
iterator(T const& f, T const& l, T start) : first{ f }, last{ l }, curr{ start } {}
2287+
2288+
auto operator*() const {
2289+
if (curr != last) { return curr; }
2290+
else { return T{}; }
2291+
}
2292+
2293+
auto operator+(int i) -> iterator {
2294+
if (i > 0) { return { first, last, std::min(curr + i, last) }; }
2295+
else { return { first, last, std::max(curr + i, 0) }; }
2296+
}
2297+
auto operator-(int i) -> iterator { return operator+(-i); }
2298+
auto operator++() -> iterator& { if (curr != last ) { ++curr; } return *this; }
2299+
auto operator--() -> iterator& { if (curr != first) { --curr; } return *this; }
2300+
auto operator++(int) -> iterator { auto old = *this; ++*this; return old; }
2301+
auto operator--(int) -> iterator { auto old = *this; ++*this; return old; }
2302+
2303+
auto operator<=>(iterator const&) const = default;
2304+
2305+
private:
2306+
T first;
2307+
T last;
2308+
T curr;
2309+
};
2310+
2311+
auto begin() const -> iterator { return iterator{ first, last, first }; }
2312+
auto end() const -> iterator { return iterator{ first, last, last }; }
2313+
auto cbegin() const -> iterator { return begin(); }
2314+
auto cend() const -> iterator { return end(); }
2315+
auto size() const -> std::size_t { return cpp2::unsafe_narrow<std::size_t>(ssize()); }
2316+
auto ssize() const -> int { return last - first; }
2317+
2318+
auto operator[](int i) const {
2319+
if (0 <= i && i < ssize()) { return first + i; }
2320+
else { return T{}; }
2321+
}
2322+
2323+
T first;
2324+
T last;
2325+
};
2326+
2327+
template<class T, class U>
2328+
range(T, U, bool = false) -> range<std::common_type_t<T, U>>;
2329+
2330+
22592331
//-----------------------------------------------------------------------
22602332
//
22612333
// alien_memory: memory typed as T but that is outside C++ and that the
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
main: () = {
2+
3+
v: std::vector =
4+
( "Aardvark", "Baboon", "Cat", "Dolphin", "Elephant", "Flicker", "Grue", "Wumpus" );
5+
6+
std::cout << "We have some alpabetical animals:\n";
7+
for v.begin()...v.end() do (e) {
8+
std::cout << " (e*)$\n";
9+
}
10+
11+
std::cout << "\nAnd from indexes 1..=5 they are:\n";
12+
for 1..=5 do (e) {
13+
std::cout << " (e)$ (v[e])$\n";
14+
}
15+
16+
all_about: std::list =
17+
( "Hokey", "Pokey" );
18+
19+
std::cout << "\nMake sure non-random-access iterators work:\n";
20+
for all_about.begin()...all_about.end() do (e) {
21+
std::cout << " (e*)$\n";
22+
}
23+
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
We have some alpabetical animals:
2+
Aardvark
3+
Baboon
4+
Cat
5+
Dolphin
6+
Elephant
7+
Flicker
8+
Grue
9+
Wumpus
10+
11+
And from indexes 1..=5 they are:
12+
1 Baboon
13+
2 Cat
14+
3 Dolphin
15+
4 Elephant
16+
5 Flicker
17+
18+
Make sure non-random-access iterators work:
19+
Hokey
20+
Pokey

regression-tests/test-results/clang-12-c++20/pure2-range-operators.cpp.output

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
We have some alpabetical animals:
2+
Aardvark
3+
Baboon
4+
Cat
5+
Dolphin
6+
Elephant
7+
Flicker
8+
Grue
9+
Wumpus
10+
11+
And from indexes 1..=5 they are:
12+
1 Baboon
13+
2 Cat
14+
3 Dolphin
15+
4 Elephant
16+
5 Flicker
17+
18+
Make sure non-random-access iterators work:
19+
Hokey
20+
Pokey

regression-tests/test-results/gcc-10-c++20/pure2-range-operators.cpp.output

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
We have some alpabetical animals:
2+
Aardvark
3+
Baboon
4+
Cat
5+
Dolphin
6+
Elephant
7+
Flicker
8+
Grue
9+
Wumpus
10+
11+
And from indexes 1..=5 they are:
12+
1 Baboon
13+
2 Cat
14+
3 Dolphin
15+
4 Elephant
16+
5 Flicker
17+
18+
Make sure non-random-access iterators work:
19+
Hokey
20+
Pokey

regression-tests/test-results/gcc-14-c++2b/pure2-range-operators.cpp.output

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
We have some alpabetical animals:
2+
Aardvark
3+
Baboon
4+
Cat
5+
Dolphin
6+
Elephant
7+
Flicker
8+
Grue
9+
Wumpus
10+
11+
And from indexes 1..=5 they are:
12+
1 Baboon
13+
2 Cat
14+
3 Dolphin
15+
4 Elephant
16+
5 Flicker
17+
18+
Make sure non-random-access iterators work:
19+
Hokey
20+
Pokey
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pure2-range-operators.cpp
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
#define CPP2_IMPORT_STD Yes
3+
4+
//=== Cpp2 type declarations ====================================================
5+
6+
7+
#include "cpp2util.h"
8+
9+
#line 1 "pure2-range-operators.cpp2"
10+
11+
12+
//=== Cpp2 type definitions and function declarations ===========================
13+
14+
#line 1 "pure2-range-operators.cpp2"
15+
auto main() -> int;
16+
17+
//=== Cpp2 function definitions =================================================
18+
19+
#line 1 "pure2-range-operators.cpp2"
20+
auto main() -> int{
21+
22+
#line 3 "pure2-range-operators.cpp2"
23+
std::vector v {
24+
"Aardvark", "Baboon", "Cat", "Dolphin", "Elephant", "Flicker", "Grue", "Wumpus"};
25+
26+
std::cout << "We have some alpabetical animals:\n";
27+
for ( auto const& e : cpp2::range(CPP2_UFCS(begin)(v),CPP2_UFCS(end)(v)) ) {
28+
std::cout << " " + cpp2::to_string(*cpp2::impl::assert_not_null(e)) + "\n";
29+
}
30+
31+
std::cout << "\nAnd from indexes 1..=5 they are:\n";
32+
for ( auto const& e : cpp2::range(1,5,true) ) {
33+
std::cout << " " + cpp2::to_string(e) + " " + cpp2::to_string(CPP2_ASSERT_IN_BOUNDS(v, e)) + "\n";
34+
}
35+
36+
std::list all_about {
37+
"Hokey", "Pokey"};
38+
39+
std::cout << "\nMake sure non-random-access iterators work:\n";
40+
for ( auto const& e : cpp2::range(CPP2_UFCS(begin)(all_about),CPP2_UFCS(end)(cpp2::move(all_about))) ) {
41+
std::cout << " " + cpp2::to_string(*cpp2::impl::assert_not_null(e)) + "\n";
42+
}
43+
44+
}
45+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pure2-range-operators.cpp2... ok (all Cpp2, passes safety checks)
2+

0 commit comments

Comments
 (0)