# Review of Autogeneration There have been known issues with auto-generated routines for a long time. Although no one has time to leap onto the problem right now, we figure people should start thinking about that. And the first part of that is to get all the grievances with the current system. # Core Features What are the core features of autogeneration, or that autogeneration allows? ## C Compatibility Old C code should continue to work without any (or mimimal). Furthermore, C-style code should usually work when mixed with CFA features. This includes behaviour not implemented as operators in CFA (such as field access and designators) as well as those that do. Note, that some CFA feature can disable C Compatibility, for instance visibility modifiers on fields might disable by-field initialization. However, orthogonal features, such as polymorphism, should not. ## Life-Time Functions We want to get the life-time functions (destructor, copy assignment and copy construction) without having to write them when they are obvious. This actually has a lot of overlap with C Compatibility, in that these are also things you can do with them in C. So these functions should act like the primitive operations in C in those cases. ## Custom Implementations We should be able to write custom implementations of the operators. These can be used to replace one of the generated functions. It also or to add a new operator for the type. ## Purposeful Missing Functions For the C-Compatibility's functions and life-time functions, sometimes we do not need, and in fact do not want, some of those functions. These should be possible to remove and any attempt to use them should be rejected on compilation. # Problems Those are the principles we have, but here are particular issues. (Thanks to Mike for producing a lot of the examples.) ## Problems With Generated Functions ### Value Call Semantics This is actually more general issue than autogenerated functions, but the the copy constructor and copy assignment operators still take their source argument by value. They have to be copied in C-style to implement the copy operator. When it is fixed, then autogeneration will have to be updated as well. Current Forms: void ?{}(char &, char); char ?=?(char &, char); New Forms: void ?{}(char &, char const &); char & ?=?(char &, char const &); ### Unused Assertions Still Added to the Assertion List All assertions on the type decklaration are used in all autogenerated functions even if they are never used. For example: The declaration of: forall(T) struct Cell { T x; } Results in the following autogenerated expands to: forall(T* | { T ?=?(T&, T); void ?{}(T&); void ?{}(T&, T); void ^?{}(T&); }) void ?{}(Cell(T)&); forall(T* | { T ?=?(T&, T); void ?{}(T&); void ?{}(T&, T); void ^?{}(T&); }) void ?{}(Cell(T)&, Cell(T)); forall(T* | { T ?=?(T&, T); void ?{}(T&); void ?{}(T&, T); void ^?{}(T&); }) void ^?{}(Cell(T)&); forall(T* | { T ?=?(T&, T); void ?{}(T&); void ?{}(T&, T); void ^?{}(T&); }) void ?=?(Cell(T)&, Cell(T)); forall(T* | { T ?=?(T&, T); void ?{}(T&); void ?{}(T&, T); void ^?{}(T&); }) void ?{}(Cell(T)&, T); If these assertions were reduced to the minimial required assertions the result would instead look something like the: forall(T* | { void ?{}(T&); }) void ?{}(Cell(T)&); forall(T* | { void ?{}(T&, T); }) void ?{}(Cell(T)&, Cell(T)); forall(T* | { void ^?{}(T&); }) void ^?{}(Cell(T)&); forall(T* | { T ?=?(T&, T); }) void ?=?(Cell(T)&, Cell(T)); forall(T* | { void ?{}(T&, T); }) void ?{}(Cell(T)&, T); This leads to exponential thunk generation for `Cell(Cell(int))` (or a matrix represented `vector(vector(vector(int)))`). ### Autogened Functions cannot use Avaible Functions If you supply an implementation for one of the autogenerated functions, it will not be used while generating other functions. Consider a case with a custom copy constructor but don't define an assignment operator. The current (problematic) behaviour reimplements the assignment operator member-wise. The ideal solution would be to create a new implementation of the operator that applies the approprate destructor, then the custom copy constructor. Although this implementation may be slower, it will have correct behaviour if the other operators are implemented properly. An alternate behaviour would simply to remove the assignment operator entirely unless the users explicity provides one. This is more similar to C++'s "The Rule of Three" (or "The Rule of Five" with move operations), where all three of the lifetime functions must be redefined if any of them are. The advantage of the new assignment operator (mentioned in the "ideal solution") is that it avoids a similar rule of three, needing only destruction and copy construction for proper lifetime behaivour. ## Problems With Removed Functions ### Failed Autogeneration Leaves Behind Declaration All autogenerated functions are a checked by attempting to resolve it. If there is an error than the autogenerated function is removed. But that only removes the definition, so it can still be considered as a candidate for resolution. The following code will compile but fail during linking. forall(T *) struct Cell { T x; }; Cell(char) s; This should be an error at resolution time, reporting that no such constructor is defined, instead of making it all the way to the linker. ### Overriding a Function can Lead to Problems Implementing your oven version of a function should always override the autogenerated function. This does not happen, especially if the declared function does not use the exact same assertions as the autogenerated function (provided via the type declaration itself). (This issue is filled as Trac Ticket 186.) ### Cannot Manually Remove Functions You cannot request that a function not be generated. The above cases could be worked around if you could. In addition, there are cases where an autogenerated routine could be created, but you do not want that operator to be be callable at all. You can delete (using `= void`) functions to mask the autogeneration functions. However the autogenerated functions still exist and you have to get the signatures exactly the same, so this is not considered practical. The main reason to manually remove functions is to enforce a behaviour based interface for a type, as opposed to a data based one. To enforce that new interface, this would have to interact with visibility. ## Other Problems & Requested Features ### Designators Designators (named parameters or keyword arguments) are nice features and being able to use them with constructors/initializers are really nice. This could be either a general solution for keyword arguments or something special for initializers. vector v = {capacity: 128}; The designator syntax (including in the example) being different from C is also a problem for compatability, but does not change their use in pure Cforall. ### Non-Intuitive Reference Initializer Initializing reference type struct members can easily catch up beginners. The following piece of code compiles without error or warning, but will lead to a segmentation fault. struct S { int & x; }; void ?{}( S & this, int & x ) { (this.x){ x }; // The correct way to implement this operation. // (&this.x){ &x }; } ### No Const Field Initialization One cannot initialize a constant field without using a cast, which makes writing constructors for types with such field more difficult. The following example does not compile. struct Const { const int x; }; void ?{}( Const & this, int x ) { (this.x){ x }; // A correct way to implement this operation. // ?{}(*(int*)&this.x, x); } (The `(*(int*)&this.x){ x };` form appears not to work for unrelated reasons.) ### New Type Parameter Shorthand A request to include another parameter shorthand for a group of assertions between sized and object-type. Notably, often we don't need to create fresh instance, we just want to manipulate existing instances and destroy them when we are done. Mike reports that for `forall(T * | [life-time-assertions])` cases he sees an approximate breakdown of: 20%: dtor 40%: dtor + copy ctor 10%: dtor + copy ctor + no-arg ctor 20%: dtor + copy ctor + custom ctor (no need for a no-arg ctor) 10%: anything else (This was not counting copy assignment, although it could be considered an optimization of destory and then copy (re)construct.) ### Incorrect Field Detection When you do write your own constructor (or destructor) any fields you do not construct (or destruct) particular fields they are automatically constructed (or destructed). But the detection is inaccurate. Exact issues are not known. But at the very least the rules are not clearly documented because no one seems to know what they are. ### No-op Constructor This may be solved, in some cases, but there is not a clear interface for not running constructors. It would be nice to, like in C, to leave stack allocated variable uninitialized, this is mostly a preformance issue but can allow you do declare a variable before the information to construct it is read. However, if a constructor is run, then all of its components should be initialized by default. ### Earlier Inline of Autogenerated Function A warning that comes up around autogenerated functions mentions static function called from inline functions. Although, this may not lead to problems, it does highlight some issues with the C initializer to Cforall constructor conversion.