-
Notifications
You must be signed in to change notification settings - Fork 0
/
p1229.bs
351 lines (269 loc) · 14.1 KB
/
p1229.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<pre class='metadata'>
Title: Labelled Parameters
Shortname: p1229
Revision: 0
Date: 2018-10-08
Editor: Jorg Brown, Google, jorg.brown@gmail.com
Abstract: This paper proposes a method for giving names to function parameters, compatible with std::forward'ing and without impact on existing code.
Status: P
Audience: EWG
Group: WG21
URL: http://wg21.link/p1229r0
Markup Shorthands: markdown yes
</pre>
Change history {#change-history}
==============
This is the second version, [[P1229R1]]
Minor edits and extra references have been added.
The first version was [[P1229R0]]
Motivating examples and previous attempts {#motivating-examples}
=========================================
Ordering of similarly-typed parameters {#sametype-params}
--------------------------------------
Consider the following code:
```
void Process(char *in, size_t bytes) {
char *buf = new char[bytes + 1];
std::memcpy(in, buf, bytes);
buf[bytes] = '\0';
ProcessCString(buf);
}
```
Due to its author's previous use of std::copy_n, where the source is the first parameter, the author here has also placed the input as the first parameter, not realizing that memcpy's arguments aren't in that order. Furthermore since the author has neglected to const-qualify the routine's "in" parameter, the mistake is not caught at compile time.
In other languages, such as python and Kotlin, parameters can be named, which allows for compile-time detection of errors. However their syntax conflicts with existing meaning since = can be used in any C++ expression:
```
std::memcpy(dst=buf, src=in, n=bytes); // three assignments and a function call.
```
Clang-tidy, among others, allows for inline documentation of parameter names, and will generate warnings if the documentation does not match the parameter names used in the routine's declaration.
```
std::memcpy(/* src= */ buf, /* dst= */ in, /* n= */ bytes); // Clang-tidy warning!
std::memcpy(/* s1= */ buf, /* s2= */ in, /* n= */ bytes); // No problem.
```
However the standard does not specify what the header file must use for parameter names, so this results in code that may not be compatible with all library implementations. Choices made in existing APIs are sometimes unfortunate; C++17 has chosen "s1", "s2", and "n" as the parameter names for memcpy, which is why clang-tidy sees no problem with the second line above.
Also, such parameter comments do not survive a std::forward call, so while there may be safety in using these name comments in most function calls, the name comments would be ignored in a call to std::make_shared().
Constructor overloads {#constructor-overloads}
---------------------
The need for named parameters is most acute in situations where functions are most overloaded: constructors. For std::pair, in fact, this was solved by adding an empty struct tag type, std::piecewise_construct_t, to select a particular overload. Similarly, std::allocator_arg_t is used for disambiguation in the constructors for std::tuple, std::function and others.
Previous proposals {#previous-proposals}
------------------
[[N4172]] "Named Arguments" also proposed a mechanism for naming arguments, with a function-call syntax identical to that proposed in this paper. However:
1. The declaration syntax was identical to current C++, basing the parameter names callers must use on the names chosen in header files. This has the unfortunate property of making currently-arbitrary naming choices part of the API for future callers. This paper adopts the view that labelling parameters should be an "opt-in" choice, and should not by default apply to existing declarations. N4172 claims that breakage due to parameter name changes would be low, because parameter names don't change that often. A bigger problem is that the very same API is often declared with different names, by different library vendors. For example, memcpy's parameters are most often declared as dest / src / n, but dst / src / n is sometimes used, as is __dst / __src / __n, and as noted above the C++ standard itself (and POSIX) uses s1 / s2 / n.
2. N4172 changes the meaning and validity of existing code when the name given to parameters in the function declaration, and the function definition, don't match. However such mismatches are quite common and may occur in library code which a user has no control over.
3. N4172 does not allow labelled parameters to be part of a variadic template function call.
4. N4172 does not allow labelled parameters to be part of function-pointer syntax.
[[N4172Discuss]] captures the discussion of the paper at Urbana-Champaign in 2014.
[[P0671R0]] "Parametric Functions" proposed a mechanism for naming arguments, where an extra "!" was used in the declaration to indicate that the names chosen for the parameters were intended to be part of the API, addressing objections 1 and 2 above. See [[P0671R0Discuss]] for other concerns raised.
[[P0671R2]] "Self-explanatory Function Arguments" is the latest revision of that paper, which removes the "!" syntax in the declaration, and specifies that the caller's argument name/label is ignored as far as the standard is concerned; this leaves it up to the implementation to warn on mismatch.
Approach {#approach}
========
For labelled parameters, this proposal adopts the empty-struct-tag approach, with one minor change: rather than a separate parameter with the tag type, we subclass the empty tag type, and add the actual parameter as a member of the subclass.
Parameter names map 1:1 to empty classes {#parameter-names-map-1-1}
----------------------------------------
The language feature needed most by this proposal is a way to define a new type on the fly when a function is declared with a labelled parameter. Ideally there would be a way to pass a literal string as a template parameter; then we could write std::label<"from"> to denote that type. Instead, we accomplish that with individual template arguments:
```
template <typename CharT, CharT... String> class label;
```
Now we can write `std::label<char, 'f', 'r', 'o', 'm'>` to get a unique type name for purposes of forming a unique argument type.
Labelled parameters derive from those empty classes {#labelled-parameters-derive}
---------------------------------------------------
To support optional use, a labelled parameter should be implicitly constructible from its unlabelled type; it must also be constructible from a labelled parameter whose type does not exactly match. So it looks like this:
```
template <typename Label, typename T>
struct labelled : public Label {
T value;
template <typename U, typename = typename std::enable_if<
std::is_convertible<U&&, T>::value>::type>
constexpr labelled(U&& u) : value(std::forward<U>(u)) {}
template <typename U>
constexpr labelled(labelled<Label, U>&& ref)
: value(std::forward<decltype(ref.value)>(ref.value)) {}
};
```
This is already enough to start using labelled parameters, however the syntax is unwieldy:
```
// declaration
void memcpy(std::labelled<std::label<char, 't', 'o'>, char *>,
std::labelled<std::label<char, 'f', 'r', 'o', 'm'>, const char *>,
std::labelled<std::label<char, 'n'>, size_t>);
// call site
memcpy(std::labelled<std::label<char, 't', 'o'>, char *>(buf),
std::labelled<std::label<char, 'f', 'r', 'o', 'm'>, const char *>(in),
std::labelled<std::label<char, 'n'>, size_t>(bytes));
```
We can improve this situation by adding a custom type and an operator() to our formerly-empty label class:
```
template <typename CharT, CharT... String>
class label {
public:
template <typename T>
using param = labelled<label, T>;
template <typename T>
constexpr labelled<label, T> operator()(T&& t) const {
return {std::forward<T>(t)};
}
};
```
And now the code is a bit more readable:
```
// declaration
void memcpy(std::label<char, 't', 'o'>::param<char *>,
std::label<char, 'f', 'r', 'o', 'm'>::param<const char *>,
std::label<char, 'n'>::param<size_t>);
// call site
memcpy(std::label<char, 't', 'o'>()(buf),
std::label<char, 'f', 'r', 'o', 'm'>()(in),
std::label<char, 'n'>()(bytes));
```
One more thing - explicit labelled parameters {#explicit-labelled-parameters}
---------------------------------------------
In some cases, the automatic conversion of a non-labelled parameter to a labelled function argument type may be undesirable. For example, in adding an overload to an existing function that accepts a pointer, if the new overload accepts an integer, then any call with the parameter 0 as an argument becomes ambiguous. But if the new overload uses an explicitly labelled parameter, then there will be no ambiguity. The new class is even simpler:
```
template <typename Label, typename T>
struct explicit_labelled : public Label {
T value;
template <typename U>
constexpr explicit_labelled(labelled<Label, U>&& ref)
: value(std::forward<decltype(ref.value)>(ref.value)) {}
};
```
Syntactic Sugar {#syntactic-sugar}
===============
While functional, the technique we have described thus far is ugly enough that most programmers would not use it. With language support it becomes much cleaner.
Labels in a Function Declaration {#labels_in_a_func}
--------------------------------
### The function call
`function-name( labelname : expression )`
is syntactically equivalent to
`function-name( std::label<char, 'l', 'a', 'b', 'e', 'l', 'n', 'a', 'm', 'e'>()(expression) )`
### Within the context of a function declaration, the parameter-declaration:
`labelname : attribute-specifier-seq-opt decl-specifier-seq declarator = initializer-clause`
is syntactically equivalent to
`attribute-specifier-seq-opt std::labelled<std::label<char, 'l', 'a', 'b', 'e', 'l', 'n', 'a', 'm', 'e'>, decl-specifier-seq> declarator = initializer-clause`
### Within the context of a function declaration, the explicit parameter-declaration:
`explicit labelname : attribute-specifier-seq-opt decl-specifier-seq declarator = initializer-clause`
is syntactically equivalent to
`attribute-specifier-seq-opt std::explicit_labelled<std::label<char, 'l', 'a', 'b', 'e', 'l', 'n', 'a', 'm', 'e'>, decl-specifier-seq> declarator = initializer-clause`
### Within the context of a function definition, the parameter name:
actually refers to the "value" field of the std::labelled struct.
This works as though the parameter had actually had a different name, and then within the function, the parameter was defined to refer to the value field. So the definition line:
`function-name(labelname : attribute-specifier-seq-opt decl-specifier-seq declarator) {`
becomes:
```
function-name(labelname : attribute-specifier-seq-opt decl-specifier-seq __declarator) {
auto &&declarator = __declarator.value;
```
where __declarator is an implementation-defined renaming of "declarator".
With this sugar, the memcpy-with-labels declaration and call becomes quite clean, even familiar to users of languages such as C# with similar syntax:
```
// declaration
void memcpy(to: char *, from: const char *, n: size_t);
// call site
memcpy(to: buf, from: in, n: bytes);
```
How close can we get today? {#how-to-today}
===========================
We can use the `std::label(char, 'l', 'a', 'b', 'e', 'l')` syntax today, to answer various questions about how this proposal works... but that gets a bit unwieldy. Fortunately we can do a little better: [[N3599]], "Literal operator templates for strings" provides a convenient mechanism currently implemented in both gcc and clang (though not officially part of C++).
```
template <typename CharT, CharT... String>
constexpr std::label<CharT, String...> operator""_label() {
return {};
}
```
Now we can write `decltype("from"_label)` to get the unique type name we need; the memcpy example becomes much more reasonable:
```
void memcpy(decltype("to"_label)::param<char *>,
decltype("from"_label)::param<const char *>,
decltype("n"_label)::param<size_t>);
// ...
void Process(char *in, size_t bytes) {
char *buf = new char[bytes + 1];
memcpy("to"_label(buf), "from"_label(in), "n"_label(bytes));
buf[bytes] = '\0';
ProcessCString(buf);
}
```
One can see these in practice at [godbolt QfIkhO](https://gcc.godbolt.org/z/QfIkhO) , which also serves to answer many questions regarding parameter matching, overload resolution, use within lambdas, std::forward propagation, etc.
Acknowledgements {#acknowledgements}
================
Thanks to Richard Smith for pointing me at N3599, and providing various other insights.
Thanks to Matthew Godbolt whose Compiler Explorer sped up the development of this proposal significantly.
<pre class=biblio>
{
"P1229R0": {
"authors": ["Jorg Brown"],
"href": "http://wg21.link/p1229r0",
"title": "Labelled Parameters",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"P1229R1": {
"authors": ["Jorg Brown"],
"href": "http://wg21.link/p1229r1",
"title": "Labelled Parameters",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"N4172": {
"authors": ["Botond Ballo"],
"href": "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm",
"title": "Named arguments",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"N4172Discuss": {
"authors": ["Axel Naumann"],
"href": "http://wiki.edg.com/bin/view/Wg21urbana-champaign/FunctionFacilities#N4172_Named_Function_Arguments",
"title": "Named arguments feedback",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"P0671R0": {
"authors": ["Axel Naumann"],
"href": "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0671r0.html",
"title": "Parametric Functions",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"P0671R0Discuss": {
"authors": ["Axel Naumann"],
"href": "http://wiki.edg.com/bin/view/Wg21toronto2017/P0671R0",
"title": "Parametric Functions Discussion",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"P0671R2": {
"authors": ["Axel Naumann"],
"href": "http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p0671r2.html",
"title": "Self-explanatory Function Arguments",
"publisher": "WG21"
}
}
</pre>
<pre class=biblio>
{
"N3599": {
"authors": ["Richard Smith"],
"href": "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3599.html",
"title": "Literal operator templates for strings",
"publisher": "WG21"
}
}
</pre>