1 module mutils.safe_union; 2 3 import std.algorithm : max; 4 import std.conv : to; 5 import std.format : format, FormatSpec, formatValue; 6 import std.meta : staticIndexOf; 7 import std.range : put; 8 import std.traits : ForeachType, hasMember, isArray, Parameters, ReturnType; 9 10 import mutils.serializer.binary; 11 12 /** 13 * Union of ConTypes... 14 * Ensures correct access with assert 15 */ 16 struct SafeUnion(bool makeFirstParDefaultOne, ConTypes...) { 17 alias FromTypes = ConTypes; 18 static assert(FromTypes.length > 0, "Union has to have members."); 19 20 union { 21 private FromTypes values; 22 } 23 24 mixin(getCode!(FromTypes)); 25 //enum Types{...} //from mixin 26 alias Types = TypesM; // alias used to give better autocompletion in IDE-s 27 28 Types currentType = (makeFirstParDefaultOne) ? Types._0 : Types.none; 29 30 /** 31 * Constuctor supporting direcs assigment of Type 32 */ 33 this(T)(T obj) { 34 static assert(properType!T, "Given Type is not present in union"); 35 set(obj); 36 } 37 38 void opAssign(SafeUnion!(makeFirstParDefaultOne, ConTypes) obj) { 39 this.tupleof = obj.tupleof; 40 } 41 //void opAssign(this); 42 void opAssign(T)(T obj) { 43 static assert(properType!T, "Given Type is not present in union"); 44 set(obj); 45 } 46 47 /** 48 * returns given type with check 49 */ 50 @nogc nothrow auto get(T)() { 51 static assert(properType!T, "Given Type is not present in union"); 52 53 enum index = staticIndexOf!(T, FromTypes); 54 assert(currentType == index, "Got type which is not currently bound."); 55 return &values[index]; 56 } 57 58 /** 59 * Returns enum value for Type 60 */ 61 @nogc nothrow bool isType(T)() { 62 static assert(properType!T, "Given Type is not present in union"); 63 enum index = staticIndexOf!(T, FromTypes); 64 return currentType == index; 65 } 66 67 /** 68 * Returns enum value for Type 69 */ 70 static Types getEnum(T)() { 71 static assert(properType!T, "Given Type is not present in union"); 72 return cast(Types) staticIndexOf!(T, FromTypes); 73 } 74 75 /** 76 * Sets given Type 77 */ 78 @nogc nothrow auto set(T)(T obj) { 79 static assert(properType!T, "Given Type is not present in union"); 80 enum index = staticIndexOf!(T, FromTypes); 81 currentType = cast(Types) index; 82 values[index] = obj; 83 } 84 85 auto ref apply(alias fun)() { 86 sw: 87 switch (currentType) { 88 foreach (i, Type; FromTypes) { 89 case i: 90 return fun(values[i]); 91 } 92 93 default: 94 assert(0); 95 } 96 } 97 98 /** 99 * Support for serialization 100 */ 101 void customSerialize(Load load, Serializer, COS)(Serializer serializer, ref COS con) { 102 auto begin = serializer.beginObject!(load)(con); 103 scope (exit) 104 serializer.endObject!(load)(con, begin); 105 106 serializer.serializeWithName!(load, "type")(currentType, con); 107 sw: 108 final switch (currentType) { 109 foreach (i, Type; FromTypes) { 110 case i: 111 serializer.serializeWithName!(load, FromTypes[i].stringof)(values[i], con); 112 break sw; 113 } 114 case Types.none: 115 break; 116 } 117 } 118 119 /** 120 * Preety print 121 */ 122 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) { 123 put(sink, "SafeUnion("); 124 125 sw: 126 final switch (currentType) { 127 foreach (i, Type; FromTypes) { 128 case i: 129 formatValue(sink, values[i], fmt); 130 break sw; 131 } 132 case Types.none: 133 put(sink, "none"); 134 break; 135 } 136 137 put(sink, ")"); 138 } 139 140 /** 141 * Checks if opDispatch supports given function 142 */ 143 static bool checkOpDispach(string funcName)() { 144 bool ok = true; 145 foreach (Type; FromTypes) { 146 ok = ok && hasMember!(Type, funcName); 147 } 148 return ok; 149 } 150 151 /** 152 * Forwards call to union member 153 * Works only if all union members has this function and this function has the same return type and parameter types 154 * Can not be made opDispatch because it somehow breakes hasMember trait 155 */ 156 auto call(string funcName, Args...)(auto ref Args args) 157 if (checkOpDispach!(funcName)) { 158 mixin("alias CompareReturnType=ReturnType!(FromTypes[0]." ~ funcName ~ ");"); 159 mixin("alias CompareParametersTypes=Parameters!(FromTypes[0]." ~ funcName ~ ");"); 160 foreach (Type; FromTypes) { 161 mixin("enum bool typeOk=is(ReturnType!(Type." ~ funcName ~ ")==CompareReturnType);"); 162 mixin( 163 "enum bool parametersOk=is(Parameters!(Type." ~ funcName 164 ~ ")==CompareParametersTypes);"); 165 static assert(typeOk, "Return type " ~ CompareReturnType.stringof 166 ~ " of '" ~ funcName ~ "' has to be the same in every union member."); 167 static assert(parametersOk, "Parameter types " ~ CompareParametersTypes.stringof 168 ~ " of '" ~ funcName ~ "' have to be the same in every union member."); 169 } 170 sw: 171 switch (currentType) { 172 foreach (i, Type; FromTypes) { 173 case i: 174 auto val = &values[i]; 175 mixin("return val." ~ funcName ~ "(args);"); 176 } 177 default: 178 assert(0); 179 } 180 } 181 182 package: 183 184 /** 185 * Generates enum for stored Types 186 */ 187 private static string getCode(FromTypes...)() { 188 189 string code = "enum TypesM:ubyte{"; 190 foreach (i, Type; FromTypes) { 191 code ~= format("_%d=%d,", i, i); 192 } 193 code ~= "none}"; 194 return code; 195 } 196 197 /** 198 * Checks if Type is in union Types 199 */ 200 private static bool properType(T)() { 201 return staticIndexOf!(T, FromTypes) != -1; 202 } 203 } 204 /// Example Usage 205 unittest { 206 struct Triangle { 207 int add(int a) { 208 return a + 10; 209 } 210 } 211 212 struct Rectangle { 213 int add(int a) { 214 return a + 100; 215 } 216 } 217 218 static uint strangeID(T)(T obj) { 219 static if (is(T == Triangle)) { 220 return 123; 221 } else static if (is(T == Rectangle)) { 222 return 14342; 223 } else { 224 assert(0); 225 } 226 } 227 228 alias Shape = SafeUnion!(false, Triangle, Rectangle); 229 Shape shp; 230 shp.set(Triangle()); 231 assert(shp.isType!Triangle); 232 assert(!shp.isType!Rectangle); 233 assert(shp.call!("add")(6) == 16); //Better error messages 234 assert(shp.apply!strangeID == 123); 235 //shp.get!(Rectangle);//Crash 236 shp.set(Rectangle()); 237 assert(shp.call!("add")(6) == 106); 238 assert(shp.apply!strangeID == 14342); 239 shp.currentType = shp.Types.none; 240 //shp.apply!strangeID;//Crash 241 //shp.add(6);//Crash 242 final switch (shp.currentType) { 243 case shp.getEnum!Triangle: 244 break; 245 case Shape.getEnum!Rectangle: 246 break; 247 case Shape.Types.none: 248 break; 249 } 250 251 }