source: doc/proposals/ @ 4260566

Last change on this file since 4260566 was fc568163, checked in by Andrew Beach <ajbeach@…>, 2 years ago

Added operator defaults proposal.

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