Skip to content

Commit 54d9b9b

Browse files
committed
Add regular _ref option to in param and forward _ return; remove oddity inout: const
Closes #876 This keeps things nicely orthogonal addresses the two open issues with parameter passing: - the oddity of `inout : const`, which becomes just `in_ref` - the need to directly express both Cpp1 `-> decltype(auto)` and `-> auto&&`, which now are `-> forward _` and `-> forward_ref _` Style Parameter Return -------- --------- ------- `in` ⭐ `inout` ✅ `out` ✅ `copy` ✅ `move` ✅ ✅ `forward` ✅ ⭐ The two ⭐'s are the cases that automatically pass/return _by value_ or reference: - all uses of `in` - deduced uses of `-> forward _` (ordinary `-> forward specific_type` always returns by reference, adding constness if there's an `in this` parameter) Now both ⭐ cases also provide a `_ref` option to not choose by-value. This addresses the two open issues with parameter passing: An example that illustrates both is std::min: min: (in_ref a, in_ref b) -> forward_ref _ = { if b < a { return b; } else { return a; } } main: (args) = { x := 456; y := 123; std::cout << min(x, y) << '\n'; } The container<T>::operator[] case could already be written like this, where the first return lowers to Cpp1 `T const&` and the second to `T&`: container: <T> type = { buf: std::array<T, 10> = (); operator[]: ( this, idx: i32) -> forward T = buf[idx]; operator[]: (inout this, idx: i32) -> forward T = buf[idx]; } main: (args) = { v: container<int> = (); std::cout << v[0] << '\n'; }
1 parent c5feb42 commit 54d9b9b

18 files changed

+734
-585
lines changed

docs/cpp2/functions.md

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ func: ( /* no parameters */ ) = { /* empty body */ }
1414
1515
## <a id="function-signatures"></a> Function signatures: Parameters, returns, and using function types
1616
17+
### <a id="parameter-kinds"></a> Overview
18+
19+
There are six kinds of function parameters, and two of them are the kinds of functions returns:
20+
21+
| Kind | Parameter | Return |
22+
| -------- | -------- | ------- |
23+
| `in` | ⭐ | |
24+
| `inout` | ✅ | |
25+
| `out` | ✅ | |
26+
| `copy` | ✅ | |
27+
| `move` | ✅ | ✅ |
28+
| `forward` | ✅ | ⭐ |
29+
30+
The two cases marked ⭐ can automatically pass/return by value or by reference, and so they can be optionally written with `_ref` to require pass/return by reference and not by value (i.e., `in_ref`, `-> forward_ref`).
31+
32+
That's it. For details, see below.
33+
1734
### <a id="parameters"></a> Parameters
1835
1936
The parameter list is a [list](common.md#lists) enclosed by `(` `)` parentheses. Each parameter is declared using the [same unified syntax](declarations.md) as used for all declarations. For example:
@@ -42,14 +59,14 @@ calc: (number: _ is std::integral) = { /*...*/ }
4259
4360
There are six ways to pass parameters that cover all use cases, that can be written before the parameter name:
4461
45-
| Parameter ***kind*** | "Pass an `x` the function ______" | Accepts arguments that are | Special semantics | ***kind*** `x: X` Compiles to Cpp1 as |
62+
| Parameter&nbsp;***kind*** | "Pass an `x` the function ______" | Accepts arguments that are | Special semantics | ***kind*** `x: X` compiles&nbsp;to&nbsp;Cpp1&nbsp;as |
4663
|---|---|---|---|---|
47-
| `in` (default) | can read from | anything | always `#!cpp const`<p>automatically passes by value if cheaply copyable | `X const x` or `X const& x` |
48-
| `copy` | gets a copy of | anything | acts like a normal local variable initialized with the argument | `X x` |
49-
| `inout` | can read from and write to | lvalues | | `X& x` |
50-
| `out` | writes to (including construct) | lvalues (including uninitialized) | must `=` assign/construct before other uses | `cpp2::impl::out<X>` |
51-
| `move` | moves from (consume the value of) | rvalues | automatically moves from every definite last use | `X&&` |
52-
| `forward` | forwards | anything | automatically forwards from every definite last use | `T&&` constrained to type `X` |
64+
| <big>**`in`**</big> (default) | can read from | anything | always `#!cpp const`<p>automatically passes by value if cheaply copyable<br><br>to guarantee a by-reference passing, use `in_ref` | `X const x` or <br> `X const& x` |
65+
| <big>**`copy`**</big> | gets a copy of | anything | acts like a normal local variable initialized with the argument | `X x` |
66+
| <big>**`inout`**</big> | can read from and write to | lvalues | | `X& x` |
67+
| <big>**`out`**</big> | writes to (including construct) | lvalues (including uninitialized) | must `=` assign/construct before other uses | `cpp2::impl::out<X>` |
68+
| <big>**`move`**</big> | moves from (consume the value of) | rvalues | automatically moves from every definite last use | `X&&` |
69+
| <big>**`forward`**</big> | forwards | anything | automatically forwards from every definite last use<br><br> for a deduced type (`-> forward _`), deduces a by-reference or by-value return (the latter if the function returns an rvalue); to guarantee a by-reference return, use `-> forward_ref _` | for&nbsp;a&nbsp;specific&nbsp;type, `-> forward T` compiles to Cpp1 `-> T&&`, and adds `const` if the function has an `in this` parameter (Cpp1 `const` member function)<br><br> for a deduced type, `-> forward _` compiles to Cpp1 `-> decltype(auto)`, and `-> forward_ref _` compiles to Cpp1 `-> auto&&` |
5370
5471
> Note: All parameters and other objects in Cpp2 are `#!cpp const` by default, except for local variables. For details, see [Design note: `#!cpp const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default).
5572
@@ -76,11 +93,19 @@ wrap_f: (
7693

7794
### <a id="return-values"></a> Return values
7895

79-
A function can return either of the following. The default is `#!cpp -> void`.
96+
A function can return either a single anonymous return value, or a return parameter list containing named return value(s). The default is `#!cpp -> void`.
8097

81-
(1) **`#!cpp -> X`** to return a single unnamed value of type `X` using the same passing styles from the [parameters](#parameters) syntax, but where the only passing styles are `move` (the default) or `forward`. The type can be `#!cpp void` to signify the function has no return value. If `X` is not `#!cpp void`, the function body must have a `#!cpp return /*value*/;` statement that returns a value of type `X` on every path that exits the function.
98+
#### Single anonymous return values
8299

83-
To deduce the return type, write `-> _`. A function whose body returns a single expression `expr` can deduce the return type, and omit writing the leading `-> _ = { return` and trailing `; }`.
100+
**`#!cpp ->` _kind_ `X`** to return a single unnamed value of type `X` using the same kinds as in the [parameters](#parameters) syntax, but where the only legal kinds are `move` (the default) or `forward` (with optional `forward_ref`; see below). The type can be `#!cpp -> void` to signify the function has no return value. If `X` is not `#!cpp void`, the function body must have a `#!cpp return /*value*/;` statement that returns a value of type `X` on every path that exits the function, or must be a single expression of type `X`.
101+
102+
To deduce the return type, write `_`:
103+
104+
- `-> _` deduces by-value return.
105+
- `-> forward _` deduces by-value return (if the function returns an rvalue) or by-reference return (everything else).
106+
- `-> forward_ref _` deduces by-reference return only.
107+
108+
A function whose body is a single expression `= expr;` defaults to `-> forward _ = { return expr; }`.
84109

85110
For example:
86111

@@ -96,9 +121,9 @@ add_one: (a: i32) -> i32 = { return a+1; }
96121
add_one: (a: i32) -> i32 = a+1;
97122

98123
// A generic function returning a single value of deduced type
99-
add: <T: type, U: type> (a:T, b:U) -> decltype(a+b) = { return a+b; }
124+
add: <T: type, U: type> (a:T, b:U) -> forward _ = { return a+b; }
100125
// Or, using syntactic defaults, the following have identical meaning:
101-
add: (a, b) -> _ = a+b;
126+
add: (a, b) -> forward _ = a+b;
102127
add: (a, b) a+b;
103128

104129
// A generic function expression returning a single value of deduced type
@@ -109,7 +134,9 @@ vec.std::ranges::sort( :(x,y) y<x );
109134
vec.std::ranges::sort( :<T:type, U:type> (x:T, y:U) -> _ = { return y<x; } );
110135
```
111136
112-
(2) **`#!cpp -> ( /* parameter list */ )`** to return a list of named return parameters using the same [parameters](#parameters) syntax, but where the only passing styles are `out` (the default, which moves where possible) or `forward`. The function body must [initialize](objects.md#init) the value of each return-parameter `ret` in its body the same way as any other local variable. An explicit return statement is written just `#!cpp return;` and returns the named values; the function has an implicit `#!cpp return;` at the end. If only a single return parameter is in the list, it is emitted in the lowered Cpp1 code the same way as (1) above, so its name is only available inside the function body.
137+
#### Return parameter lists: Nameable return value(s)
138+
139+
**`#!cpp -> ( /* parameter list */ )`** to return a list of named return parameters using the same [parameters](#parameters) syntax, but where the only needed kinds are `out` (the default, which moves where possible) or `forward`. The function body must [initialize](objects.md#init) the value of each return-parameter `ret` in its body the same way as any other local variable. An explicit return statement is written just `#!cpp return;` and returns the named values; the function has an implicit `#!cpp return;` at the end. If only a single return parameter is in the list, it is emitted in the lowered Cpp1 code the same way as a single anonymous return value above, so its name is only available inside the function body.
113140
114141
For example:
115142
@@ -256,7 +283,7 @@ else {
256283

257284
**`#!cpp do`** and **`#!cpp while`** are like always in C++, except that `(` `)` parentheses around the condition are not required. Instead, `{` `}` braces around the loop body *are* required.
258285

259-
**`#!cpp for range do (e)`** ***statement*** says "for each element in `range`, call it `e` and perform the statement." The loop parameter `(e)` is an ordinary parameter that can be passed using any [parameter passing style](#parameters); as always, the default is `in`, which is read-only and expresses a read-only loop. The statement is not required to be enclosed in braces.
286+
**`#!cpp for range do (e)`** ***statement*** says "for each element in `range`, call it `e` and perform the statement." The loop parameter `(e)` is an ordinary parameter that can be passed using any [parameter kinds](#parameters); as always, the default is `in`, which is read-only and expresses a read-only loop. The statement is not required to be enclosed in braces.
260287

261288
Every loop can have a `next` clause, that is performed at the end of each loop body execution. This makes it easy to have a counter for any loop, including a range `#!cpp for` loop.
262289

@@ -433,9 +460,9 @@ Next, `: _` is also the default parameter type, so we don't need to write even t
433460
equals: (in a, in b) -> _ = { return a == b; }
434461
```
435462

436-
Next, `in` is the default [parameter passing mode](#parameters). So we can use that default too:
463+
Next, `in` is the default [parameter kind](#parameters). So we can use that default too:
437464

438-
``` cpp title="equals: Identical meaning, now using the 'in' default parameter passing style"
465+
``` cpp title="equals: Identical meaning, now using the 'in' default parameter kind"
439466
equals: (a, b) -> _ = { return a == b; }
440467
```
441468

regression-tests/mixed-parameter-passing.cpp2

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,21 @@ parameter_styles: (
3737

3838
}
3939

40-
main: () -> int = { }
40+
min: (in_ref a, in_ref b) -> forward_ref _
41+
= { if b < a { return b; } else { return a; } }
42+
43+
container: <T> type = {
44+
buf: std::array<T, 10> = ();
45+
operator[]: ( this, idx: i32) -> forward T = buf[idx];
46+
operator[]: (inout this, idx: i32) -> forward T = buf[idx];
47+
}
48+
49+
main: () = {
50+
x := 456;
51+
y := 123;
52+
std::cout << min(x, y) << '\n';
53+
54+
v: container<int> = ();
55+
std::cout << v[0] << '\n';
56+
}
57+

regression-tests/pure2-last-use.cpp2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ issue_850: () = {
159159
v: std::vector = ( 1, 2, 3, 4, 5 );
160160

161161
// Definite last use of v => move-capture v into f's closure
162-
f := :() -> forward _ = { return v$; };
162+
f := :() -> forward_ref _ = { return v$; };
163163

164164
// Now we can access the vector captured inside f()...
165165
f().push_back(6);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
123
2+
0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
123
2+
0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
123
2+
0

regression-tests/test-results/mixed-parameter-passing.cpp

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
#line 1 "mixed-parameter-passing.cpp2"
99

10+
#line 43 "mixed-parameter-passing.cpp2"
11+
template<typename T> class container;
12+
1013

1114
//=== Cpp2 type definitions and function declarations ===========================
1215

@@ -27,7 +30,24 @@ auto parameter_styles(
2730
) -> void;
2831

2932
#line 40 "mixed-parameter-passing.cpp2"
30-
[[nodiscard]] auto main() -> int;
33+
[[nodiscard]] auto min(auto const& a, auto const& b) -> auto&&;
34+
35+
#line 43 "mixed-parameter-passing.cpp2"
36+
template<typename T> class container {
37+
private: std::array<T,10> buf {};
38+
public: [[nodiscard]] auto operator[](cpp2::impl::in<cpp2::i32> idx) const& -> T const&;
39+
public: [[nodiscard]] auto operator[](cpp2::impl::in<cpp2::i32> idx) & -> T&;
40+
public: container() = default;
41+
public: container(container const&) = delete; /* No 'that' constructor, suppress copy */
42+
public: auto operator=(container const&) -> void = delete;
43+
44+
#line 47 "mixed-parameter-passing.cpp2"
45+
};
46+
47+
auto main() -> int;
48+
#line 57 "mixed-parameter-passing.cpp2"
49+
50+
#line 1 "mixed-parameter-passing.cpp2"
3151

3252
//=== Cpp2 function definitions =================================================
3353

@@ -70,5 +90,21 @@ auto parameter_styles(
7090
}
7191

7292
#line 40 "mixed-parameter-passing.cpp2"
73-
[[nodiscard]] auto main() -> int{}
93+
[[nodiscard]] auto min(auto const& a, auto const& b) -> auto&&
94+
{if (cpp2::impl::cmp_less(b,a)) {return b; }else {return a; }}
95+
96+
#line 45 "mixed-parameter-passing.cpp2"
97+
template <typename T> [[nodiscard]] auto container<T>::operator[](cpp2::impl::in<cpp2::i32> idx) const& -> T const& { return CPP2_ASSERT_IN_BOUNDS(buf, idx); }
98+
#line 46 "mixed-parameter-passing.cpp2"
99+
template <typename T> [[nodiscard]] auto container<T>::operator[](cpp2::impl::in<cpp2::i32> idx) & -> T& { return CPP2_ASSERT_IN_BOUNDS(buf, idx); }
100+
101+
#line 49 "mixed-parameter-passing.cpp2"
102+
auto main() -> int{
103+
auto x {456};
104+
auto y {123};
105+
std::cout << min(cpp2::move(x), cpp2::move(y)) << '\n';
106+
107+
container<int> v {};
108+
std::cout << CPP2_ASSERT_IN_BOUNDS_LITERAL(cpp2::move(v), 0) << '\n';
109+
}
74110

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
123
2+
0

regression-tests/test-results/pure2-bugfix-for-ufcs-name-lookup.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace ns {
2828
#line 1 "pure2-bugfix-for-ufcs-name-lookup.cpp2"
2929
class identity {
3030
#line 2 "pure2-bugfix-for-ufcs-name-lookup.cpp2"
31-
public: [[nodiscard]] constexpr auto operator()(auto&& x) const& -> auto&&;
31+
public: [[nodiscard]] constexpr auto operator()(auto&& x) const& -> decltype(auto);
3232
};
3333

3434
class t {
@@ -53,7 +53,7 @@ auto main() -> int;
5353
#line 1 "pure2-bugfix-for-ufcs-name-lookup.cpp2"
5454

5555
#line 2 "pure2-bugfix-for-ufcs-name-lookup.cpp2"
56-
[[nodiscard]] constexpr auto identity::operator()(auto&& x) const& -> auto&& { return CPP2_FORWARD(x); }
56+
[[nodiscard]] constexpr auto identity::operator()(auto&& x) const& -> decltype(auto) { return CPP2_FORWARD(x); }
5757

5858
#line 6 "pure2-bugfix-for-ufcs-name-lookup.cpp2"
5959
[[nodiscard]] constexpr auto t::f() const& -> int { return 0; }

regression-tests/test-results/pure2-for-loop-range-with-lambda.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
//=== Cpp2 type definitions and function declarations ===========================
1313

1414
#line 1 "pure2-for-loop-range-with-lambda.cpp2"
15-
[[nodiscard]] auto first(auto&& f, [[maybe_unused]] auto&& ...unnamed_param_2) -> auto&&;
15+
[[nodiscard]] auto first(auto&& f, [[maybe_unused]] auto&& ...unnamed_param_2) -> decltype(auto);
1616

1717
#line 3 "pure2-for-loop-range-with-lambda.cpp2"
1818
auto main(int const argc_, char** argv_) -> int;
1919

2020
//=== Cpp2 function definitions =================================================
2121

2222
#line 1 "pure2-for-loop-range-with-lambda.cpp2"
23-
[[nodiscard]] auto first(auto&& f, [[maybe_unused]] auto&& ...unnamed_param_2) -> auto&& { return CPP2_FORWARD(f); }
23+
[[nodiscard]] auto first(auto&& f, [[maybe_unused]] auto&& ...unnamed_param_2) -> decltype(auto) { return CPP2_FORWARD(f); }
2424

2525
#line 3 "pure2-for-loop-range-with-lambda.cpp2"
2626
auto main(int const argc_, char** argv_) -> int{

regression-tests/test-results/pure2-forward-return.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#line 1 "pure2-forward-return.cpp2"
1515

1616
#line 2 "pure2-forward-return.cpp2"
17-
[[nodiscard]] auto first(auto&& rng) -> auto&&;
17+
[[nodiscard]] auto first(auto&& rng) -> decltype(auto);
1818

1919
#line 7 "pure2-forward-return.cpp2"
2020
extern int const global;
@@ -27,7 +27,7 @@ extern int const global;
2727
#line 1 "pure2-forward-return.cpp2"
2828

2929
#line 2 "pure2-forward-return.cpp2"
30-
[[nodiscard]] auto first(auto&& rng) -> auto&& {
30+
[[nodiscard]] auto first(auto&& rng) -> decltype(auto) {
3131
if (cpp2::bounds_safety.is_active() && !(!(std::empty(rng))) ) { cpp2::bounds_safety.report_violation(""); }
3232

3333
#line 5 "pure2-forward-return.cpp2"

regression-tests/test-results/pure2-last-use.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ auto f_inout([[maybe_unused]] auto& unnamed_param_1) -> void;
8181
auto f_copy([[maybe_unused]] auto ...unnamed_param_1) -> void;
8282
[[nodiscard]] auto pred([[maybe_unused]] auto const& ...unnamed_param_1) -> auto;
8383
[[nodiscard]] auto pred_copy([[maybe_unused]] auto ...unnamed_param_1) -> auto;
84-
template<typename T> [[nodiscard]] constexpr auto identity(T&& x) -> auto&&
84+
template<typename T> [[nodiscard]] constexpr auto identity(T&& x) -> decltype(auto)
8585
CPP2_REQUIRES (std::is_reference_v<T>) ;
8686
#line 6 "pure2-last-use.cpp2"
8787
[[nodiscard]] auto identity_copy(auto x) -> auto
@@ -560,7 +560,7 @@ auto f_copy([[maybe_unused]] auto ...unnamed_param_1) -> void{}
560560
#line 4 "pure2-last-use.cpp2"
561561
[[nodiscard]] auto pred_copy([[maybe_unused]] auto ...unnamed_param_1) -> auto { return true; }
562562
#line 5 "pure2-last-use.cpp2"
563-
template<typename T> [[nodiscard]] constexpr auto identity(T&& x) -> auto&&
563+
template<typename T> [[nodiscard]] constexpr auto identity(T&& x) -> decltype(auto)
564564
requires (std::is_reference_v<T>) {return CPP2_FORWARD(x); }
565565
#line 6 "pure2-last-use.cpp2"
566566
[[nodiscard]] auto identity_copy(auto x) -> auto
@@ -1430,7 +1430,7 @@ namespace captures {
14301430
auto f() -> void{
14311431
auto x {cpp2_new<int>(0)};
14321432
f_copy(std::move(cpp2::move(x)));
1433-
auto id {[](auto const& x) -> auto&& { return x; }};
1433+
auto id {[](auto const& x) -> decltype(auto) { return x; }};
14341434
auto y {cpp2_new<int>(0)};
14351435
if (cpp2::cpp2_default.is_active() && !(&cpp2::move(id)(y) == &y) ) { cpp2::cpp2_default.report_violation(""); }
14361436
}

regression-tests/test-results/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
cppfront compiler v0.7.4 Build 9930:1408
2+
cppfront compiler v0.7.4 Build 9A03:1357
33
Copyright(c) Herb Sutter All rights reserved
44

55
SPDX-License-Identifier: CC-BY-NC-ND-4.0

source/build.info

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"9930:1408"
1+
"9A03:1357"

0 commit comments

Comments
 (0)