1 | Operator Defaults |
---|
2 | ================= |
---|
3 | |
---|
4 | This proposal introduces a new syntax for requesting a default implementation |
---|
5 | of an operator and the rules for generating them. |
---|
6 | |
---|
7 | This is to build on the implied relationships between operators. Most default |
---|
8 | operators will be implemented in terms of other operators. As a fall-back |
---|
9 | operators can try to figure out an intuitive definition from inspection of the |
---|
10 | type's sue declaration. |
---|
11 | |
---|
12 | Syntax |
---|
13 | ------ |
---|
14 | |
---|
15 | The syntax for requesting a default implementation is part of function |
---|
16 | definition. After the function signature (after the parameter list) instead |
---|
17 | of putting the function body you declare it to be equal to default. |
---|
18 | |
---|
19 | box ?+?(box const & left, box const & right) = default; |
---|
20 | |
---|
21 | These can exist along side any forward definitions of the function, but would |
---|
22 | conflict with any other complete definitions or deletions of the function. |
---|
23 | |
---|
24 | It could be valid syntax on any function, but possibly all non-operators would |
---|
25 | report that no default implementation available. |
---|
26 | |
---|
27 | If default implementations are really popular and we don't need additional |
---|
28 | information about the signature a more compact syntax could be added. |
---|
29 | |
---|
30 | default box( ?(), ?=?, ?!=?, ?<?, ?>?, ?<=?, ?>=? ); |
---|
31 | |
---|
32 | Generation Strategies |
---|
33 | --------------------- |
---|
34 | |
---|
35 | There exists a system around the default generation that selects how to |
---|
36 | generate a given function if one can be generated at all. This section |
---|
37 | describes that system and some of the logic behind it. |
---|
38 | |
---|
39 | There are two main strategies for generating an operator implementation. |
---|
40 | |
---|
41 | The first is to mimic the relationship between operators on the primitive |
---|
42 | types by defining a new operator in terms of an existing operator on the same |
---|
43 | type. For instance `++i` is equivalent to `i += 1`, so the generated |
---|
44 | implementation will "look like" that. |
---|
45 | |
---|
46 | The second is to inspect the structure of the declaration to guess at what |
---|
47 | the default implementation would be. Because of that it is dependent on the |
---|
48 | kind of declaration, a structure doesn't have the same rules as an |
---|
49 | enumeration. Also it is similar to the implicate definitions currently created |
---|
50 | by the compiler and the generation methods for many are carried over. |
---|
51 | |
---|
52 | From these strategies a method of generation (a particular set of rules that |
---|
53 | define an implementation from the type and existing functions) has to be |
---|
54 | picked, but often there is more than on reasonable choice. In these cases they |
---|
55 | are ordered and the first (best) one whose requirements is met is then used. |
---|
56 | See "Circular Requirements" below for some exceptions and extensions to this |
---|
57 | pattern. |
---|
58 | |
---|
59 | Generally the methods based on operators come first as they propagate any |
---|
60 | unusual implementations from the explicate operators to the ones being |
---|
61 | generated. If all of those fail then the intuitive definition based on the |
---|
62 | declaration's shape is used. The general patterns in this area for the |
---|
63 | different sue types follow. |
---|
64 | |
---|
65 | ### Structures |
---|
66 | Structures will usually apply the operation to each field, or when there are |
---|
67 | two parameters the matching pairs of fields from each, and then combine the |
---|
68 | results. |
---|
69 | |
---|
70 | This does require that the fields have certain operators defined on them. |
---|
71 | In this respect it is still operator based generation, but we use inspection |
---|
72 | on the structure to find out which operators to use. |
---|
73 | |
---|
74 | Also, for the purposes of default generation types declared with the |
---|
75 | concurrency modifiers (coroutine, monitor and thread) are considered structs. |
---|
76 | The default implementations should be the same as if you had written out the |
---|
77 | extra field and functions by hand. |
---|
78 | |
---|
79 | ### Enumerations |
---|
80 | The two ways of using enumerations are considered. First as "one of" the |
---|
81 | list options as in normal use, the second is as a set of flags where each |
---|
82 | option represents a flag that may or may not be set. |
---|
83 | |
---|
84 | Currently there is no way to specify which nor does the system attempt to |
---|
85 | guess by checking assigned values. There is one case where an operator could |
---|
86 | have a meaningful default in both versions. If both are included then we can |
---|
87 | try to pick one by scanning the enumeration to see what values its options |
---|
88 | are given (a linear series or powers of 2) could be used. In all other cases |
---|
89 | the definition that makes sense can be assumed. |
---|
90 | |
---|
91 | ### Unions |
---|
92 | Unions are the hardest to deal with because the instance does not show which |
---|
93 | field in the union is being used. Because of that there are very few intuitive |
---|
94 | definitions to use and the ones that do depend on bit-wise operations and only |
---|
95 | if the union is made of primitive types. |
---|
96 | |
---|
97 | ### Traits |
---|
98 | Default operations are not supported on traits. A function implemented by the |
---|
99 | default generation may be used to satisfy an assertion. However a default |
---|
100 | implementation may not be requested on a polymorphic function. |
---|
101 | |
---|
102 | It could in theory, limiting to operation based generation and using the |
---|
103 | operations available in the assertion list. There are a few problems: |
---|
104 | + Knowing the entire set of functions being generated is very useful in some |
---|
105 | cases and this information is quickly lost with polymorphic functions. |
---|
106 | + The rules for choosing a generation method do not match how a polymorphic |
---|
107 | function is selected so the results can be inconsistent. |
---|
108 | + It is easily to mimic with a polymorphic function already, writing out one |
---|
109 | generic function and including it. |
---|
110 | |
---|
111 | Default Generation |
---|
112 | ------------------ |
---|
113 | |
---|
114 | Here are the generation methods. Unless otherwise stated they are listed in |
---|
115 | priority order. That is the first one mentioned that a type fits (has all the |
---|
116 | required operators or its form matches) will be used. |
---|
117 | |
---|
118 | The operator based constructions can be used on any sue type, those that |
---|
119 | require a particular kind mention that. |
---|
120 | |
---|
121 | ### Constructor: ?{} |
---|
122 | Note that requesting any constructor to be generated counts as defining a |
---|
123 | constructor for purposes of disabling the implicate constructors. There are |
---|
124 | no operator based methods for generating constructors. |
---|
125 | |
---|
126 | For structures: For the zero argument constructor (aka the default constructor, |
---|
127 | which takes just a reference to the value to construct) each field is |
---|
128 | constructed with its zero argument constructor. For the copy constructor each |
---|
129 | field is copy constructed from the same field in the copied structure. For |
---|
130 | zero_t and one_t each field is also constructed from zero_t or one_t. |
---|
131 | |
---|
132 | For enumerations: For the zero argument constructor the value is set to 0 if |
---|
133 | one of the enumeration options is 0 (or set to the first value in the enum). |
---|
134 | For the zero_t constructor it is the same except the check is skipped (and no |
---|
135 | or). The copy constructor is the same as memcpy. |
---|
136 | |
---|
137 | For unions: For the zero argument constructor of a union that is constructed |
---|
138 | entirely of primitive types (or other types that zero argument construct to |
---|
139 | all 0s) the union filled with 0s. For the copy constructor of a union of types |
---|
140 | that all have trivial copy constructors memcpy is used. |
---|
141 | |
---|
142 | ### Destructor: ^?{} |
---|
143 | Requesting the default destructor should be the same as having it implicitly |
---|
144 | defined. Destructors only have one signature and the intuitive definition for |
---|
145 | that is the same as without the signature. |
---|
146 | |
---|
147 | Still it should be allowed for consistency. It also allows it to be forward |
---|
148 | declared and then generated in a .cfa file. |
---|
149 | |
---|
150 | ### Assignment: ?=? |
---|
151 | Default assignment is only supported between two objects of the same type. |
---|
152 | For structures it is field to field assignment. For enumerations and unions |
---|
153 | of primitives or trivially copiable types it the same as memcpy. |
---|
154 | |
---|
155 | ### Equality: ?==? ?!=? |
---|
156 | Both equality operations can be implemented by negating the result of the |
---|
157 | other operations. |
---|
158 | |
---|
159 | For structures: Equality can be implement by checking equality on each pair of |
---|
160 | matching fields and taking the logical and of the results. Inequality can be |
---|
161 | implemented by checking inequality on each pair of matching fields and taking |
---|
162 | the logical or of the results. |
---|
163 | |
---|
164 | Both logical operations could be short circuiting. Without side effects it is |
---|
165 | purely an optimization. |
---|
166 | |
---|
167 | For enumerations: Both operations are the same as on the underlying integral. |
---|
168 | |
---|
169 | For unions: If it is assumed that the different branches represent different |
---|
170 | views of the same data and this data is primitive, than bit-wise comparisons |
---|
171 | can be used. |
---|
172 | |
---|
173 | ### Comparison: ?<? ?>? ?<=? ?>=? |
---|
174 | Less than can be implemented by flipping the arguments on greater than. |
---|
175 | Greater than can be implemented by flipping less then. Less than or equal to |
---|
176 | can be implemented by flipping greater than or equal to. Greater than or equal |
---|
177 | to can be implemented by flipping less than or equal to. |
---|
178 | |
---|
179 | Less than or equal to can be implemented by using less than, equals and taking |
---|
180 | the or of the results. Greater than or equal to can be implemented by using |
---|
181 | greater than, equals and taking the or of the results. |
---|
182 | |
---|
183 | > The trick of negating comparisons is not used. As an example ?<? is not |
---|
184 | > (boolean) not ?>=? unless the type is strictly ordered. For operator based |
---|
185 | > overloads that might not be true in very reasonable implementations so it is |
---|
186 | > not assumed. |
---|
187 | |
---|
188 | Opposite less than can be implemented as less than or equal to and not equal |
---|
189 | to. Greater than can be implemented as greater than or equal to and not equal |
---|
190 | to. |
---|
191 | |
---|
192 | For enumerations: Enumerations that represent one of all operations are the |
---|
193 | same as on the underlying integral. Enumerations that represent a set of |
---|
194 | options could replace less then with subset of and greator than with superset |
---|
195 | of and use bit-wise masking to implement those operations. |
---|
196 | |
---|
197 | ### Binary & Relative Assignment Operators: ?_? ?_=? |
---|
198 | This applies to each operator in the form of `T ?_?(T, T);` for some type T |
---|
199 | and has a matching relative assignment operator `T& ?_=?(T&, T)` where the |
---|
200 | `_` in both is replaced by some string of operator characters. |
---|
201 | |
---|
202 | The binary operator can be created by copying the left argument, using the |
---|
203 | relative assignment with the right argument and returning the updated copy. |
---|
204 | |
---|
205 | The relative assignment operator can be implemented by using the binary |
---|
206 | operation to create a copy, then assigning the result to the left argument. |
---|
207 | The left argument should then be returned. For the signature above it would |
---|
208 | return by reference. The signature `T ?_=?(T&, T)` could also be supported in |
---|
209 | which case it would return by copy. |
---|
210 | |
---|
211 | ### Minus: -? ?-? |
---|
212 | Unary minus can be implemented by subtracting argument from the value created |
---|
213 | from zero_t. |
---|
214 | |
---|
215 | Binary minus can be implemented by negating (with unary minus) the right |
---|
216 | argument and adding the result to the left argument. |
---|
217 | |
---|
218 | ### Increment & Decrement: ++? ?++ --? ?-- |
---|
219 | Either pre- operation can implemented by applying the post- operation and then |
---|
220 | returning a reference to, or copy of, the updated value. Either post- |
---|
221 | operation can be implemented by copying the argument, applying the pre- |
---|
222 | operation to the original and returning the copy. |
---|
223 | |
---|
224 | Pre-increment can be implemented by using addition assignment by the value |
---|
225 | constructed from one_t. Pre-decrement can be implemented by using subtraction |
---|
226 | assignment by the value from one_t. |
---|
227 | |
---|
228 | Because many of the operations used have there own default implementations |
---|
229 | (for example: ?+=? from ?+? or field-wise one_t construction) this list could |
---|
230 | be expanded by replacing a function call with the default implementation for |
---|
231 | that function. This might work for the post- operations generated from |
---|
232 | relative assignment, the one_t constructed object and copying, but the logical |
---|
233 | connection becomes weaker and weaker as that process continues. |
---|
234 | |
---|
235 | ### Bit Manipulation: ?|? ?&? ~? ?<<? ?>>? |
---|
236 | No operation based construction is provided for bit manipulation operators. |
---|
237 | |
---|
238 | For enumerations that are considered sets of flags: And returns a set with all |
---|
239 | flags set in both operand sets, or returns a set with all flags set that are |
---|
240 | set in either operand sets and not returns a set with all flags set that are |
---|
241 | not set in the operand set. |
---|
242 | |
---|
243 | > And it would be possible to actually implement this for any sized type and |
---|
244 | > just do the bitwise operation and trust the user that it makes sense if they |
---|
245 | > request it. |
---|
246 | |
---|
247 | Also for set enumerations, the signature `T ?<<?(one_t, unsigned)` also has |
---|
248 | some use, as it can be used to implement a loop that goes over each flag, |
---|
249 | instead of each combination of flags. |
---|
250 | |
---|
251 | ### Logical Negation: !? |
---|
252 | Not can be implemented by negating the result of a conversion to boolean, the |
---|
253 | does not equal 0 test. |
---|
254 | |
---|
255 | Circular Requirements |
---|
256 | --------------------- |
---|
257 | |
---|
258 | There are several cases where there are two operators that can be implemented |
---|
259 | with the other operator. If both are implemented that way calling either of |
---|
260 | them could result in infinite recursion. |
---|
261 | |
---|
262 | The simplest way to handle the issue would be to tell the user to not do that, |
---|
263 | they are responsible for providing the base operations. This is C like but is |
---|
264 | perhaps more error prone than it would save in work and if we do check we can |
---|
265 | automatically use fallbacks. |
---|
266 | |
---|
267 | Before generating any default implementations the compiler should generate |
---|
268 | a list of everything it has been requested and ignore any generation methods |
---|
269 | that would lead to chains instead of counting other defaults that could lead |
---|
270 | to a loop. As a further improvment this could be done selectively to break |
---|
271 | rings while allowing chains of non-recurive implementations. |
---|
272 | |
---|
273 | For instance if both ?==? and ?!=? are requested they cannot both be defined |
---|
274 | as the negation of each other. In the simple version they would both be |
---|
275 | generated by introspection on the declaration. With the more selective version |
---|
276 | one could be generated by introspection and the other by negating that result. |
---|
277 | |
---|
278 | However there are still ways to get around this by placing the function |
---|
279 | definitions in different translation units or defining a function that uses |
---|
280 | an operator that is default generated to use it. Searching for all these cases |
---|
281 | is probably not worth it, although checking for some might be useful warnings. |
---|