1 static assert(!hasComplexAssignment!int); 2 static assert(!hasComplexAssignment!real); 3 static assert(!hasComplexAssignment!string); 4 static assert(!hasComplexAssignment!(int[])); 5 static assert(!hasComplexAssignment!(int[42])); 6 static assert(!hasComplexAssignment!(int[string])); 7 static assert(!hasComplexAssignment!Object); 8 9 static struct NoOpAssign 10 { 11 int i; 12 } 13 static assert(!hasComplexAssignment!NoOpAssign); 14 15 // For complex assignment, the parameter type must match the type of the 16 // struct (with compatible qualifiers), but refness does not matter (though 17 // it will obviously affect whether rvalues will be accepted as well as 18 // whether non-copyable types will be accepted). 19 static struct HasOpAssign 20 { 21 void opAssign(HasOpAssign) {} 22 } 23 static assert( hasComplexAssignment!HasOpAssign); 24 static assert(!hasComplexAssignment!(const(HasOpAssign))); 25 26 static struct HasOpAssignRef 27 { 28 void opAssign(ref HasOpAssignRef) {} 29 } 30 static assert( hasComplexAssignment!HasOpAssignRef); 31 static assert(!hasComplexAssignment!(const(HasOpAssignRef))); 32 33 static struct HasOpAssignAutoRef 34 { 35 void opAssign()(auto ref HasOpAssignAutoRef) {} 36 } 37 static assert( hasComplexAssignment!HasOpAssignAutoRef); 38 static assert(!hasComplexAssignment!(const(HasOpAssignAutoRef))); 39 40 // Assigning a mutable value works when opAssign takes const, because 41 // mutable implicitly converts to const, but assigning to a const variable 42 // does not work, so normally, a const object is not considered to have 43 // complex assignment. 44 static struct HasOpAssignC 45 { 46 void opAssign(const HasOpAssignC) {} 47 } 48 static assert( hasComplexAssignment!HasOpAssignC); 49 static assert(!hasComplexAssignment!(const(HasOpAssignC))); 50 51 // If opAssign is const, then assigning to a const variable will work, and a 52 // const object will have complex assignment. However, such a type would 53 // not normally make sense, since it can't actually be mutated by opAssign. 54 static struct HasConstOpAssignC 55 { 56 void opAssign(const HasConstOpAssignC) const {} 57 } 58 static assert( hasComplexAssignment!HasConstOpAssignC); 59 static assert( hasComplexAssignment!(const(HasConstOpAssignC))); 60 61 // For a type to have complex assignment, the types must match aside from 62 // the qualifiers. So, an opAssign which takes another type does not count 63 // as complex assignment. 64 static struct OtherOpAssign 65 { 66 void opAssign(int) {} 67 } 68 static assert(!hasComplexAssignment!OtherOpAssign); 69 70 // The return type doesn't matter for complex assignment, though normally, 71 // opAssign should either return a reference to the this reference (so that 72 // assignments can be chained) or void. 73 static struct HasOpAssignWeirdRet 74 { 75 int opAssign(HasOpAssignWeirdRet) { return 42; } 76 } 77 static assert( hasComplexAssignment!HasOpAssignWeirdRet); 78 79 // The compiler will generate an assignment operator if a member variable 80 // has one. 81 static struct HasMemberWithOpAssign 82 { 83 HasOpAssign s; 84 } 85 static assert( hasComplexAssignment!HasMemberWithOpAssign); 86 87 // The compiler will generate an assignment operator if the type has a 88 // postblit constructor or a destructor. 89 static struct HasDtor 90 { 91 ~this() {} 92 } 93 static assert( hasComplexAssignment!HasDtor); 94 95 // If a struct has @disabled opAssign (and thus assigning to a variable of 96 // that type will result in a compilation error), then 97 // hasComplexAssignment is false. 98 // Code that wants to check whether assignment works will need to test that 99 // assigning to a variable of that type compiles (which could need to test 100 // both an lvalue and an rvalue depending on the exact sort of assignment 101 // the code is actually going to do). 102 static struct DisabledOpAssign 103 { 104 @disable void opAssign(DisabledOpAssign); 105 } 106 static assert(!hasComplexAssignment!DisabledOpAssign); 107 static assert(!__traits(compiles, { DisabledOpAssign s; 108 s = rvalueOf!DisabledOpAssign; 109 s = lvalueOf!DisabledOpAssign; })); 110 static assert(!is(typeof({ DisabledOpAssign s; 111 s = rvalueOf!DisabledOpAssign; 112 s = lvalueOf!DisabledOpAssign; }))); 113 114 // Static arrays have complex assignment if their elements do. 115 static assert( hasComplexAssignment!(HasOpAssign[1])); 116 117 // Static arrays with no elements do not have complex assignment, because 118 // there's nothing to assign to. 119 static assert(!hasComplexAssignment!(HasOpAssign[0])); 120 121 // Dynamic arrays do not have complex assignment, because assigning to them 122 // just slices them rather than assigning to their elements. Assigning to 123 // an array with a slice operation - e.g. arr[0 .. 5] = other[0 .. 5]; - 124 // does use opAssign if the elements have it, but since assigning to the 125 // array itself does not, hasComplexAssignment is false for dynamic arrays. 126 static assert(!hasComplexAssignment!(HasOpAssign[])); 127 128 // Classes and unions do not have complex assignment even if they have 129 // members which do. 130 class C 131 { 132 HasOpAssign s; 133 } 134 static assert(!hasComplexAssignment!C); 135 136 union U 137 { 138 HasOpAssign s; 139 } 140 static assert(!hasComplexAssignment!U); 141 142 // https://issues.dlang.org/show_bug.cgi?id=24833 143 // This static assertion fails, because the compiler 144 // currently ignores assignment operators for enum types. 145 enum E : HasOpAssign { a = HasOpAssign.init } 146 //static assert( hasComplexAssignment!E);
Whether assigning to a variable of the given type involves either a user-defined opAssign or a compiler-generated opAssign rather than using the default assignment behavior (which would use memcpy). The opAssign must accept the same type (with compatible qualifiers) as the type which the opAssign is declared on for it to count for hasComplexAssignment.
The compiler will generate an opAssign for a struct when a member variable of that struct defines an opAssign. It will also generate one when the struct has a postblit constructor or destructor (and those can be either user-defined or compiler-generated).
However, due to https://issues.dlang.org/show_bug.cgi?id=24834, the compiler does not currently generate an opAssign for structs that define a copy constructor, and so hasComplexAssignment is false for such types unless they have an explicit opAssign, or the compiler generates one due to a member variable having an opAssign.
Note that hasComplexAssignment is also true for static arrays whose element type has an opAssign, since while the static array itself does not have an opAssign, the compiler must use the opAssign of the elements when assigning to the static array.
Due to https://issues.dlang.org/show_bug.cgi?id=24833, enums never have complex assignment even if their base type does. Their opAssign is never called, resulting in incorrect behavior for such enums. So, because the compiler does not treat them as having complex assignment, hasComplexAssignment is false for them.
No other types (including class references, pointers, and unions) ever have an opAssign and thus hasComplexAssignment is never true for them. It is particularly important to note that unions never have an opAssign, so if a struct contains a union which contains one or more members which have an opAssign, that struct will have to have a user-defined opAssign which explicitly assigns to the correct member of the union if you don't want the current value of the union to simply be memcopied when assigning to the struct.
One big reason that code would need to worry about hasComplexAssignment is if void initialization is used anywhere. While it might be okay to assign to uninitialized memory for a type where assignment does a memcopy, assigning to uninitialized memory will cause serious issues with any opAssign which looks at the object before assigning to it (e.g. because the type uses reference counting). In such cases, core.sys.lifetime.copyEmplace needs to be used instead of assignment.