-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathP1749-access-control-for-reflection.bs
211 lines (166 loc) · 8.57 KB
/
P1749-access-control-for-reflection.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
<pre class='metadata'>
Title: Access control for reflection
Status: D
Audience: SG7 (Reflection)
Editor: Yehezkel Bernat, [email protected]
Shortname: P1749
Abstract: P1749 claims that reflection must be constrained in regular code and obey access control and scope rules
Group: WG21
Date: 2019-06-14
Markup Shorthands: markdown yes
Revision: 0
Default Highlight: CPP
ED: https://yehezkelshb.github.io/cpp_proposals/P1749-access-control-for-reflection.html
</pre>
Problem statement {#problem}
=================
In "The Design & Evolution of C++", there is a section about "Keyword Arguments"
(section 6.5.1, page 153), a language extension proposed in the process of the
first standardization of C++. It was about "a mechanism for specifying function
arguments by name" in the call site, e.g. `new window(Color:=green, ysize:=150);`.
Bjarne Stroustrup mentioned there were a few concerns and issues, which were the
reasons to reject this extension (pg. 155 and on). Reading it, the author found
that some of the arguments are still relevant today and apply to reflection.
Here are some of those arguments and some comments, perspectives and thoughts
about them and how they are relevant for reflection.
ODR - One *Declaration* Rule {#odr}
----------------------------
> The first serious problem discovered with the proposal was that keyword
> arguments would introduce a new form of binding between a calling interface
> and an implementation:
>
> [1] An argument must have the same name in a function declaration as in the
> function definition.
>
> [...]
>
> Worse, this turned our to be a compatibility problem of significant magnitude.
> Some organizations recommend a style with "long, informative" argument names
> in header files, and "short, convenient" names in the definitions. For example:
> ```
> void reverse(int* elements, int length_of_elements_array);
> // ...
> void reverse(int* v, int n)
> {
> // ...
> }
> ```
> Naturally, some people find that style abhorrent, whereas others (including me)
> find it quite reasonable. Apparently, significant amounts of such code exist.
[p0670r2](https://wg21.link/p0670r2) says:
> This concern is mitigated in two ways:
> 1. Modern coding conventions have the declarations for a particular function
> showing up in exactly one header file.
> 2. Modern coding conventions discourage the use of different argument names
> between function declarations (in a header) and function definitions (in a
> '.cpp' file). Dedicated compiler warnings exist to protect against this case.
So we don't think modern code is using such a coding guideline anymore.
The question is why we would want to add a new way of "ODR-violation" sort of
issues into the language, and help the potentially breakage of older code that
was written to these guidelines.
> Alternatively, the language shouldn't require declarations to have the same
> name for the same argument. That seemed viable to me. However, people didn't
> seem to like that variant either.
>
> There could be a noticeable impact on link times if the rule that arguments
> names must match across compilation units is checked. If it isn't checked, the
> facility would not be type safe and could become a source of subtle errors.
So he considered such facility type unsafe. An interesting point.
Backward compatibility and API breaking {#api}
---------------------------------------
> [2] Once a keyword argument is used, the name of that argument cannot be
> changed in the function definition without breaking user code.
>
> [...]
>
> Further, an implication of keyword arguments would be that no name in a
> commonly distributed head file could be changes without risking breaking code.
We are making the parameter names part of the public API of any library. Library
vendors will not be able to fix a typo in the parameter name anymore, as this
will be a breaking change for the users.
You may argue that any mistake in the API is hard to fix. This is true, but
usually we can have them both. We can keep the older function name with the typo
and introduce a new function with the correct name (probably `[[deprecate]]`-ing
the old one). We can add a type alias for a mistake in the type name. We can add
a new overloading for the function with stronger types for the arguments. None
of these solutions is relevant for argument names. We'll have to introduce a new
function name (FuncEx, anyone?) just to fix the parameter name!
Portability and standardization issues {#portability}
--------------------------------------
> Different suppliers of header files for common services (for example, Posix or
> X) would also have to agree on argument names. This could easily become a
> bureaucratic nightmare.
Do we want to specify the parameter names of all the standard library functions
and force them on the implementations? [\SD-8](https://isocpp.org/std/standing-documents/sd-8-standard-library-compatibility)
tries hard to free the standard library from various possible backward
compatibility issues, and allowing reflection on parameter names seems like
adding a new one.
Effect on readability {#readability}
---------------------
> Both the potential linking cost and the very real binding problem could be
> easily avoided by omitting argument names in header files. A cautious user
> might therefore avoid specifying argument names in header files. Thus, to
> quote Bill Gibbons, "The net impact on readability of C++ might actually be
> negative."
Considering the previous 2 points, I think this one talks for itself.
Of course, for template functions, even this escape hatch isn't available...
Encouraging bad code techniques {#techniques}
-------------------------------
> My main worry about keyword arguments was actually that keyword arguments
> might slow the gradual transition from traditional programming techniques to
> data abstraction and object-oriented programming in C++. In code that I find
> best written and easiest to maintain, long argument lists are very rare. In
> fact, it is a common observation that a transition to a more object-oriented
> style leads to a significant decrease in the length of argument lists; [...]
>
> [...]
>
> A further reduction in the number of arguments could be obtained by using a
> `Point` type rather than expressing interfaces directly in terms of coordinates.
p0670r2 brings as an example the following code:
```
double Gauss(double x, double mean, double width, double height);
```
Do we want to encourage this style? Don't we want to teach that usage of strong
types? This is true now more than ever as Metaclasses will allow easy creation
of strong typedefs, without resorting to more mouthful library-based options.
Inconsistency with other parts of the standard {#inconsistency}
----------------------------------------------
p0542 explicitly permits redeclaration of a function with contracts to have
different naming for the arguments:
```
int f(int x)
[[expects: x>0]]
[[ensures r: r>0]];
int f(int y)
[[expects: y>0]] // Should be OK
[[ensures z: z>0]]; // Should be OK
```
This seems inconsistent, where in one part of the standard we explicitly allow
different naming of function arguments and in another part we forbid it.
Proposed solution {#solution}
=================
The solution proposed here is to allow reflection in a regular code to access
only entities that are already accessible from the same code. Specifically it
means:
- Parameter names are accessible from inside the function only (allowing the
use-case of generating diagnostic messages)
- The regular access control is applied when accessing class members
The last point comes from the observation that API issues mentioned above are
relevant for the reflection on non-public class members too.
What will be allowed is to reflect on everything (including private members and
parameter names) from a metfunction or metaclass. This allows all the use-cases
of generating various language bindings etc. while preventing all the mentioned
issues from regular user code.
Metaclass is a different creature anyway. It's usually used by the type author,
and even if we allow the `.as` operator to apply a metaclass on already existing
type, this is a special operation that is known to be more fragile.
Acknowledgements {#acknowledgements}
================
Thanks for David Sankel for listening to my arguments and encouraging me to
write this paper.
References {#ref}
==========
- [p0670r2](https://wg21.link/p0670r2) - Static reflection of functions
- [p0542](https://wg21.link/p0542) - Support for contract based programming in C++
- [\SD-8](https://isocpp.org/std/standing-documents/sd-8-standard-library-compatibility) - Standard Library Compatibility