Operator Defaults ================= This proposal introduces a new syntax for requesting a default implementation of an operator and the rules for generating them. This is to build on the implied relationships between operators. Most default operators will be implemented in terms of other operators. As a fall-back operators can try to figure out an intuitive definition from inspection of the type's sue declaration. Syntax ------ The syntax for requesting a default implementation is part of function definition. After the function signature (after the parameter list) instead of putting the function body you declare it to be equal to default. box ?+?(box const & left, box const & right) = default; These can exist along side any forward definitions of the function, but would conflict with any other complete definitions or deletions of the function. It could be valid syntax on any function, but possibly all non-operators would report that no default implementation available. If default implementations are really popular and we don't need additional information about the signature a more compact syntax could be added. default box( ?(), ?=?, ?!=?, ??, ?<=?, ?>=? ); Generation Strategies --------------------- There exists a system around the default generation that selects how to generate a given function if one can be generated at all. This section describes that system and some of the logic behind it. There are two main strategies for generating an operator implementation. The first is to mimic the relationship between operators on the primitive types by defining a new operator in terms of an existing operator on the same type. For instance `++i` is equivalent to `i += 1`, so the generated implementation will "look like" that. The second is to inspect the structure of the declaration to guess at what the default implementation would be. Because of that it is dependent on the kind of declaration, a structure doesn't have the same rules as an enumeration. Also it is similar to the implicate definitions currently created by the compiler and the generation methods for many are carried over. From these strategies a method of generation (a particular set of rules that define an implementation from the type and existing functions) has to be picked, but often there is more than on reasonable choice. In these cases they are ordered and the first (best) one whose requirements is met is then used. See "Circular Requirements" below for some exceptions and extensions to this pattern. Generally the methods based on operators come first as they propagate any unusual implementations from the explicate operators to the ones being generated. If all of those fail then the intuitive definition based on the declaration's shape is used. The general patterns in this area for the different sue types follow. ### Structures Structures will usually apply the operation to each field, or when there are two parameters the matching pairs of fields from each, and then combine the results. This does require that the fields have certain operators defined on them. In this respect it is still operator based generation, but we use inspection on the structure to find out which operators to use. Also, for the purposes of default generation types declared with the concurrency modifiers (coroutine, monitor and thread) are considered structs. The default implementations should be the same as if you had written out the extra field and functions by hand. ### Enumerations The two ways of using enumerations are considered. First as "one of" the list options as in normal use, the second is as a set of flags where each option represents a flag that may or may not be set. Currently there is no way to specify which nor does the system attempt to guess by checking assigned values. There is one case where an operator could have a meaningful default in both versions. If both are included then we can try to pick one by scanning the enumeration to see what values its options are given (a linear series or powers of 2) could be used. In all other cases the definition that makes sense can be assumed. ### Unions Unions are the hardest to deal with because the instance does not show which field in the union is being used. Because of that there are very few intuitive definitions to use and the ones that do depend on bit-wise operations and only if the union is made of primitive types. ### Traits Default operations are not supported on traits. A function implemented by the default generation may be used to satisfy an assertion. However a default implementation may not be requested on a polymorphic function. It could in theory, limiting to operation based generation and using the operations available in the assertion list. There are a few problems: + Knowing the entire set of functions being generated is very useful in some cases and this information is quickly lost with polymorphic functions. + The rules for choosing a generation method do not match how a polymorphic function is selected so the results can be inconsistent. + It is easily to mimic with a polymorphic function already, writing out one generic function and including it. Default Generation ------------------ Here are the generation methods. Unless otherwise stated they are listed in priority order. That is the first one mentioned that a type fits (has all the required operators or its form matches) will be used. The operator based constructions can be used on any sue type, those that require a particular kind mention that. ### Constructor: ?{} Note that requesting any constructor to be generated counts as defining a constructor for purposes of disabling the implicate constructors. There are no operator based methods for generating constructors. For structures: For the zero argument constructor (aka the default constructor, which takes just a reference to the value to construct) each field is constructed with its zero argument constructor. For the copy constructor each field is copy constructed from the same field in the copied structure. For zero_t and one_t each field is also constructed from zero_t or one_t. For enumerations: For the zero argument constructor the value is set to 0 if one of the enumeration options is 0 (or set to the first value in the enum). For the zero_t constructor it is the same except the check is skipped (and no or). The copy constructor is the same as memcpy. For unions: For the zero argument constructor of a union that is constructed entirely of primitive types (or other types that zero argument construct to all 0s) the union filled with 0s. For the copy constructor of a union of types that all have trivial copy constructors memcpy is used. ### Destructor: ^?{} Requesting the default destructor should be the same as having it implicitly defined. Destructors only have one signature and the intuitive definition for that is the same as without the signature. Still it should be allowed for consistency. It also allows it to be forward declared and then generated in a .cfa file. ### Assignment: ?=? Default assignment is only supported between two objects of the same type. For structures it is field to field assignment. For enumerations and unions of primitives or trivially copiable types it the same as memcpy. ### Equality: ?==? ?!=? Both equality operations can be implemented by negating the result of the other operations. For structures: Equality can be implement by checking equality on each pair of matching fields and taking the logical and of the results. Inequality can be implemented by checking inequality on each pair of matching fields and taking the logical or of the results. Both logical operations could be short circuiting. Without side effects it is purely an optimization. For enumerations: Both operations are the same as on the underlying integral. For unions: If it is assumed that the different branches represent different views of the same data and this data is primitive, than bit-wise comparisons can be used. ### Comparison: ?? ?<=? ?>=? Less than can be implemented by flipping the arguments on greater than. Greater than can be implemented by flipping less then. Less than or equal to can be implemented by flipping greater than or equal to. Greater than or equal to can be implemented by flipping less than or equal to. Less than or equal to can be implemented by using less than, equals and taking the or of the results. Greater than or equal to can be implemented by using greater than, equals and taking the or of the results. > The trick of negating comparisons is not used. As an example ? (boolean) not ?>=? unless the type is strictly ordered. For operator based > overloads that might not be true in very reasonable implementations so it is > not assumed. Opposite less than can be implemented as less than or equal to and not equal to. Greater than can be implemented as greater than or equal to and not equal to. For enumerations: Enumerations that represent one of all operations are the same as on the underlying integral. Enumerations that represent a set of options could replace less then with subset of and greator than with superset of and use bit-wise masking to implement those operations. ### Binary & Relative Assignment Operators: ?_? ?_=? This applies to each operator in the form of `T ?_?(T, T);` for some type T and has a matching relative assignment operator `T& ?_=?(T&, T)` where the `_` in both is replaced by some string of operator characters. The binary operator can be created by copying the left argument, using the relative assignment with the right argument and returning the updated copy. The relative assignment operator can be implemented by using the binary operation to create a copy, then assigning the result to the left argument. The left argument should then be returned. For the signature above it would return by reference. The signature `T ?_=?(T&, T)` could also be supported in which case it would return by copy. ### Minus: -? ?-? Unary minus can be implemented by subtracting argument from the value created from zero_t. Binary minus can be implemented by negating (with unary minus) the right argument and adding the result to the left argument. ### Increment & Decrement: ++? ?++ --? ?-- Either pre- operation can implemented by applying the post- operation and then returning a reference to, or copy of, the updated value. Either post- operation can be implemented by copying the argument, applying the pre- operation to the original and returning the copy. Pre-increment can be implemented by using addition assignment by the value constructed from one_t. Pre-decrement can be implemented by using subtraction assignment by the value from one_t. Because many of the operations used have there own default implementations (for example: ?+=? from ?+? or field-wise one_t construction) this list could be expanded by replacing a function call with the default implementation for that function. This might work for the post- operations generated from relative assignment, the one_t constructed object and copying, but the logical connection becomes weaker and weaker as that process continues. ### Bit Manipulation: ?|? ?&? ~? ?<>? No operation based construction is provided for bit manipulation operators. For enumerations that are considered sets of flags: And returns a set with all flags set in both operand sets, or returns a set with all flags set that are set in either operand sets and not returns a set with all flags set that are not set in the operand set. > And it would be possible to actually implement this for any sized type and > just do the bitwise operation and trust the user that it makes sense if they > request it. Also for set enumerations, the signature `T ?<