1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Type constructors. 7 * 8 * This module contains templates that allow to build new types from the 9 * available ones. 10 * 11 * Copyright: Eugene Wissner 2017-2020. 12 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 13 * Mozilla Public License, v. 2.0). 14 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 15 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/typecons.d, 16 * tanya/typecons.d) 17 */ 18 module tanya.typecons; 19 20 import tanya.format; 21 import tanya.memory.lifetime; 22 import tanya.meta.metafunction; 23 import tanya.meta.trait; 24 25 /** 26 * $(D_PSYMBOL Tuple) can store two or more heterogeneous objects. 27 * 28 * The objects can by accessed by index as `obj[0]` and `obj[1]` or by optional 29 * names (e.g. `obj.first`). 30 * 31 * $(D_PARAM Specs) contains a list of object types and names. First 32 * comes the object type, then an optional string containing the name. 33 * If you want the object be accessible only by its index (`0` or `1`), 34 * just skip the name. 35 * 36 * Params: 37 * Specs = Field types and names. 38 * 39 * See_Also: $(D_PSYMBOL tuple). 40 */ 41 template Tuple(Specs...) 42 { 43 template parseSpecs(size_t fieldCount, Specs...) 44 { 45 static if (Specs.length == 0) 46 { 47 alias parseSpecs = AliasSeq!(); 48 } 49 else static if (is(Specs[0]) && fieldCount < 2) 50 { 51 static if (is(typeof(Specs[1]) == string)) 52 { 53 alias parseSpecs 54 = AliasSeq!(Pack!(Specs[0], Specs[1]), 55 parseSpecs!(fieldCount + 1, Specs[2 .. $])); 56 } 57 else 58 { 59 alias parseSpecs 60 = AliasSeq!(Pack!(Specs[0]), 61 parseSpecs!(fieldCount + 1, Specs[1 .. $])); 62 } 63 } 64 else 65 { 66 static assert(false, "Invalid argument: " ~ Specs[0].stringof); 67 } 68 } 69 70 alias ChooseType(alias T) = T.Seq[0]; 71 alias ParsedSpecs = parseSpecs!(0, Specs); 72 73 static assert(ParsedSpecs.length > 1, "Invalid argument count"); 74 75 private string formatAliases(size_t n, Specs...)() 76 { 77 static if (Specs.length == 0) 78 { 79 return ""; 80 } 81 else 82 { 83 string fieldAlias; 84 static if (Specs[0].length == 2) 85 { 86 char[21] buffer; 87 fieldAlias = "alias " ~ Specs[0][1] ~ " = expand[" 88 ~ integral2String(n, buffer).idup ~ "];"; 89 } 90 return fieldAlias ~ formatAliases!(n + 1, Specs[1 .. $])(); 91 } 92 } 93 94 struct Tuple 95 { 96 /// Field types. 97 alias Types = Map!(ChooseType, ParsedSpecs); 98 99 // Create field aliases. 100 mixin(formatAliases!(0, ParsedSpecs[0 .. $])()); 101 102 /// Represents the values of the $(D_PSYMBOL Tuple) as a list of values. 103 Types expand; 104 105 alias expand this; 106 } 107 } 108 109 /// 110 @nogc nothrow pure @safe unittest 111 { 112 auto pair = Tuple!(int, "first", string, "second")(1, "second"); 113 assert(pair.first == 1); 114 assert(pair[0] == 1); 115 assert(pair.second == "second"); 116 assert(pair[1] == "second"); 117 } 118 119 /** 120 * Creates a new $(D_PSYMBOL Tuple). 121 * 122 * Params: 123 * Names = Field names. 124 * 125 * See_Also: $(D_PSYMBOL Tuple). 126 */ 127 template tuple(Names...) 128 { 129 /** 130 * Creates a new $(D_PSYMBOL Tuple). 131 * 132 * Params: 133 * Args = Field types. 134 * args = Field values. 135 * 136 * Returns: Newly created $(D_PSYMBOL Tuple). 137 */ 138 auto tuple(Args...)(auto ref Args args) 139 if (Args.length >= Names.length && isTypeTuple!Args) 140 { 141 alias Zipped = ZipWith!(AliasSeq, Pack!Args, Pack!Names); 142 alias Nameless = Args[Names.length .. $]; 143 144 return Tuple!(Zipped, Nameless)(forward!args); 145 } 146 } 147 148 /// 149 @nogc nothrow pure @safe unittest 150 { 151 auto t = tuple!("one", "two")(20, 5); 152 assert(t.one == 20); 153 assert(t.two == 5); 154 } 155 156 /** 157 * Type that can hold one of the types listed as its template parameters. 158 * 159 * $(D_PSYMBOL Variant) is a type similar to $(D_KEYWORD union), but 160 * $(D_PSYMBOL Variant) keeps track of the actually used type and throws an 161 * assertion error when trying to access an invalid type at runtime. 162 * 163 * Params: 164 * Specs = Types this $(D_SPYBMOL Variant) can hold. 165 */ 166 template Variant(Specs...) 167 if (isTypeTuple!Specs && NoDuplicates!Specs.length == Specs.length) 168 { 169 union AlignedUnion(Args...) 170 { 171 static if (Args.length > 0) 172 { 173 Args[0] value; 174 } 175 static if (Args.length > 1) 176 { 177 AlignedUnion!(Args[1 .. $]) rest; 178 } 179 } 180 181 private struct VariantAccessorInfo 182 { 183 string accessor; 184 ptrdiff_t tag; 185 } 186 187 template accessor(T, Union) 188 { 189 enum VariantAccessorInfo info = accessorImpl!(T, Union, 1); 190 enum accessor = VariantAccessorInfo("this.values" ~ info.accessor, info.tag); 191 } 192 193 template accessorImpl(T, Union, size_t tag) 194 { 195 static if (is(T == typeof(Union.value))) 196 { 197 enum accessorImpl = VariantAccessorInfo(".value", tag); 198 } 199 else 200 { 201 enum VariantAccessorInfo info = accessorImpl!(T, typeof(Union.rest), tag + 1); 202 enum accessorImpl = VariantAccessorInfo(".rest" ~ info.accessor, info.tag); 203 } 204 } 205 206 struct Variant 207 { 208 /// Types can be present in this $(D_PSYMBOL Variant). 209 alias Types = Specs; 210 211 private ptrdiff_t tag = -1; 212 private AlignedUnion!Types values; 213 214 /** 215 * Constructs this $(D_PSYMBOL Variant) with one of the types supported 216 * in it. 217 * 218 * Params: 219 * T = Type of the initial value. 220 * value = Initial value. 221 */ 222 this(T)(ref T value) 223 if (canFind!(T, Types)) 224 { 225 copyAssign!T(value); 226 } 227 228 /// ditto 229 this(T)(T value) 230 if (canFind!(T, Types)) 231 { 232 moveAssign!T(value); 233 } 234 235 ~this() 236 { 237 reset(); 238 } 239 240 this(this) 241 { 242 alias pred(U) = hasElaborateCopyConstructor!(U.Seq[1]); 243 static foreach (Type; Filter!(pred, Enumerate!Types)) 244 { 245 if (this.tag == Type.Seq[0]) 246 { 247 get!(Type.Seq[1]).__postblit(); 248 } 249 } 250 } 251 252 /** 253 * Tells whether this $(D_PSYMBOL Variant) is initialized. 254 * 255 * Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) contains a 256 * value, $(D_KEYWORD false) otherwise. 257 */ 258 bool hasValue() const 259 { 260 return this.tag != -1; 261 } 262 263 /** 264 * Tells whether this $(D_PSYMBOL Variant) holds currently a value of 265 * type $(D_PARAM T). 266 * 267 * Params: 268 * T = Examined type. 269 * 270 * Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) currently 271 * contains a value of type $(D_PARAM T), $(D_KEYWORD false) 272 * otherwise. 273 */ 274 bool peek(T)() const 275 if (canFind!(T, Types)) 276 { 277 return this.tag == staticIndexOf!(T, Types); 278 } 279 280 /** 281 * Returns the underlying value, assuming it is of the type $(D_PARAM T). 282 * 283 * Params: 284 * T = Type of the value should be returned. 285 * 286 * Returns: The underyling value. 287 * 288 * Precondition: The $(D_PSYMBOL Variant) has a value. 289 * 290 * See_Also: $(D_PSYMBOL peek), $(D_PSYMBOL hasValue). 291 */ 292 ref inout(T) get(T)() inout 293 if (canFind!(T, Types)) 294 in 295 { 296 assert(this.tag == staticIndexOf!(T, Types), "Variant isn't initialized"); 297 } 298 do 299 { 300 mixin("return " ~ accessor!(T, AlignedUnion!Types).accessor ~ ";"); 301 } 302 303 /** 304 * Reassigns the value. 305 * 306 * Params: 307 * T = Type of the new value 308 * that = New value. 309 * 310 * Returns: $(D_KEYWORD this). 311 */ 312 ref typeof(this) opAssign(T)(T that) 313 if (canFind!(T, Types)) 314 { 315 reset(); 316 return moveAssign!T(that); 317 } 318 319 /// ditto 320 ref typeof(this) opAssign(T)(ref T that) 321 if (canFind!(T, Types)) 322 { 323 reset(); 324 return copyAssign!T(that); 325 } 326 327 private ref typeof(this) moveAssign(T)(ref T that) @trusted 328 { 329 this.tag = staticIndexOf!(T, Types); 330 331 enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor; 332 moveEmplace(that, mixin(accessorMixin)); 333 334 return this; 335 } 336 337 private ref typeof(this) copyAssign(T)(ref T that) return 338 { 339 this.tag = staticIndexOf!(T, Types); 340 341 enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor; 342 emplace!T((() @trusted => (&mixin(accessorMixin))[0 .. 1])(), that); 343 344 return this; 345 } 346 347 private void reset() 348 { 349 alias pred(U) = hasElaborateDestructor!(U.Seq[1]); 350 static foreach (Type; Filter!(pred, Enumerate!Types)) 351 { 352 if (this.tag == Type.Seq[0]) 353 { 354 destroy(get!(Type.Seq[1])); 355 } 356 } 357 } 358 359 /** 360 * Returns $(D_PSYMBOL TypeInfo) corresponding to the current type. 361 * 362 * If this $(D_PSYMBOL Variant) isn't initialized, returns 363 * $(D_KEYWORD null). 364 * 365 * Returns: $(D_PSYMBOL TypeInfo) of the current type. 366 */ 367 @property TypeInfo type() 368 { 369 static foreach (i, Type; Types) 370 { 371 if (this.tag == i) 372 { 373 return typeid(Type); 374 } 375 } 376 return null; 377 } 378 379 /** 380 * Compares this $(D_PSYMBOL Variant) with another one with the same 381 * specification for equality. 382 * 383 * $(UL 384 * $(LI If both hold values of the same type, these values are 385 * compared.) 386 * $(LI If they hold values of different types, then the 387 * $(D_PSYMBOL Variant)s aren't equal.) 388 * $(LI If only one of them is initialized but another one not, they 389 * aren't equal.) 390 * $(LI If neither of them is initialized, they are equal.) 391 * ) 392 * 393 * Params: 394 * that = The $(D_PSYMBOL Variant) to compare with. 395 * 396 * Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) is equal to 397 * $(D_PARAM that), $(D_KEYWORD false) otherwise. 398 */ 399 bool opEquals()(auto ref inout(Variant) that) inout 400 { 401 if (this.tag != that.tag) 402 { 403 return false; 404 } 405 static foreach (i, Type; Types) 406 { 407 if (this.tag == i) 408 { 409 return get!Type == that.get!Type; 410 } 411 } 412 return true; 413 } 414 } 415 } 416 417 /// 418 @nogc nothrow pure @safe unittest 419 { 420 Variant!(int, double) variant = 5; 421 assert(variant.peek!int); 422 assert(variant.get!int == 5); 423 424 variant = 5.4; 425 assert(!variant.peek!int); 426 assert(variant.get!double == 5.4); 427 }