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 }