Refining Parameter Passing Semantics Suggestions below attempt to: - Extend semantic clarity to the forwarding of value category and cv-qualification. - Extract passing mechanism control, re-focusing the categories on semantics. - Present a practical option for call-site passing semantics visibility. - Enable convenient selection between modifying and non-modifying overloads. - Enhance clarity and consistency. ### The **forward** category obscures semantics The forward category models pass-by-forwarding-reference, followed by std::forward on last use of the parameter, allowing the compiler to perform overload resolution when passing the parameter on, based on the value category (l-value or r-value) and cv-qualification (const or not) of the argument that was passed in. But such a forwarding mechanism is ambiguous with regard to whether it takes arguments to conditionally **move** or to conditionally modify. The **move** and **inout** categories are used for fundamentally different purposes, and **forward** obfuscates which one it's representing in a given instance. Instead of **forward**: **_Consider using_ "move?"** (conditional move) when the argument will be moved or not, depending on whether or not the argument is an r-value. **_Consider using_ "inout?"** (conditional modification) when the argument will be modified or not, depending on whether or not the argument is **const** (see the suggestion for better naming below). ### **copy** and **in_ref** are about passing mechanisms, not semantics Herb's [parameter passing paper](https://github.com/hsutter/708/blob/main/708.pdf) gives multiple reasons why, when an argument is guaranteed not to be modified, the compiler should be allowed to decide whether to pass by value or reference. Ultimately though, it can't be argued that programmers should be prohibited from deciding themselves. So for forcing pass-by-value, cpp2 now has a **copy** passing category. And for forcing pass-by-const-reference, it now has **in_ref**. These undermine the philosophy of organizing the categories around semantics rather than passing mechanism. **_Consider, instead, using modifiers to force a given passing mechanism._** foo: (/*in*/ bar: T) // The compiler chooses the passing mechanism. bar is const. foo: (/*in*/ & bar: T) // Pass by reference. bar remains const. foo: (/*in*/ = bar: T) // Pass by value. bar becomes a local variable. Regardless of the passing mechanism used, "**in**" semantics is honored. Arguments are not modified. ### Call-site Visibility Herb 's [parameter passing paper](https://github.com/hsutter/708/blob/main/708.pdf), poses the question of how parameter passing semantics can reasonably be made visible at the call site. Probably the most helpful information to make visible is _**argument modified**_ and _**argument moved**_. While **move**s have the **move** keyword, modifications have no such call site indicator. A succinct syntactic solution, which is symmetrical (ie. consistent) between the two cases, may be a better approach. **_Consider requiring postfix_ "+" _in order to pass a modifiable l-value for_ inout _or_ out** (treating modifiable l-values as **const** arguments unless so marked): swap(a+, b+); // a and b will be modified. c: = mymap+[d]; // mymap may be modified. d is treated as read-only. * If one were unaware that `map::operator []` may modify the map it's called on, the compiler would make them aware when they tried to use it. To call it, they must explicitly mark the map for modification (`mymap+`), which then also makes it clear at a glance to the reader. * Or where overloads are available, _**this syntax enables convenient selection of modifying overloads**_ (like the **move** keyword enables selection of moving overloads). * Assignment operators and constructors unambiguously signal intent to modify their left hand operands (`a += b; c: = d;`), so no further call-site visual indication is necessary. However, decoration should still be required when assignment operators are invoked as functions rather than used as operators (`operator =(e+, f);`). **_Consider requiring postfix_ "-" _in order to pass a modifiable l-value for_ move**: a: = b-; // b will be moved. myvector.push_back(c-); // c will be moved. * Using language syntax for this is already a positive change from a library function, but this approach minimizes syntactic impact at the call site. * These call site visual indicators are consistent with each other, and together they probably represent the maximum semantic information that can be unobtrusively included at the call site. ### Naming While this is understandably a taboo topic, there are real issues with naming here. In an early talk on parameter passing, Herb mentioned that people often suggest that "out parameters" shouldn't be supported, and his [parameter passing paper](https://github.com/hsutter/708/blob/main/708.pdf) quotes examples which mistakenly use **out** for **inout** use-cases. These issues likely stem from common conceptions of "out parameters" which are at odds with the semantics of this category. The term is decades old and predates any annotation or linting scheme that could prohibit a function from reading an argument. Mentions of moving objects, above, are paired with mentions of the **move** category, but mentions of modifying objects can't be similarly paired (resulting in the awkwardly named suggestion, "**inout?**", above). If the categories were consistently named for actions to be taken on arguments (like **move**), an instructor could say something like, "A function may **init**, **mod**, **move**, or just **read** a given argument. It may **mod** or **move** conditionally. And the passing mechanism can be forced for **read**ing." This language is more natural, consistent, expressive, and clear. It seems to better represent "What, not how" parameter passing. And it side-steps issues around historical understandings of "out parameters". The impact of these suggestions probably can't be measured, but since ease of teaching, learning, and using the language is a primary goal of cpp2, I believe they should be considered.