1 module nxt.typecons_ex;
2 
3 /+ TODO: Move to Phobos and refer to http://forum.dlang.org/thread/lzyqywovlmdseqgqfvun@forum.dlang.org#post-ibvkvjwexdafpgtsamut:40forum.dlang.org +/
4 /+ TODO: Better with?: +/
5 /* inout(Nullable!T) nullable(T)(inout T a) */
6 /* { */
7 /*	 return typeof(return)(a); */
8 /* } */
9 /* inout(Nullable!(T, nullValue)) nullable(alias nullValue, T)(inout T value) */
10 /* if (is (typeof(nullValue) == T)) */
11 /* { */
12 /*	 return typeof(return)(value); */
13 /* } */
14 
15 import std.typecons : Nullable, NullableRef;
16 
17 /** Instantiator for `Nullable`.
18  */
19 auto nullable(T)(T a)
20 {
21 	return Nullable!T(a);
22 }
23 
24 ///
25 pure nothrow @safe @nogc unittest {
26 	auto x = 42.5.nullable;
27 	assert(is(typeof(x) == Nullable!double));
28 }
29 
30 /** Instantiator for `Nullable`.
31 */
32 auto nullable(alias nullValue, T)(T value)
33 if (is (typeof(nullValue) == T))
34 {
35 	return Nullable!(T, nullValue)(value);
36 }
37 
38 ///
39 pure nothrow @safe @nogc unittest {
40 	auto x = 3.nullable!(int.max);
41 	assert(is (typeof(x) == Nullable!(int, int.max)));
42 }
43 
44 /** Instantiator for `NullableRef`.
45  */
46 auto nullableRef(T)(T* a) @safe pure nothrow
47 {
48 	return NullableRef!T(a);
49 }
50 
51 ///
52 /*TODO: @safe*/ pure nothrow @nogc unittest {
53 	auto x = 42.5;
54 	auto xr = nullableRef(&x);
55 	assert(!xr.isNull);
56 	xr.nullify;
57 	assert(xr.isNull);
58 }
59 
60 /** See_Also: http://forum.dlang.org/thread/jwdbjlobbilowlnpdzzo@forum.dlang.org
61  */
62 template New(T)
63 if (is(T == class))
64 {
65 	T New(Args...) (Args args)
66 	{
67 		return new T(args);
68 	}
69 }
70 
71 import std.traits : isArray, isUnsigned;
72 import std.range.primitives : hasSlicing;
73 
74 /** Check if `T` is castable to `U`.
75  */
76 enum isCastableTo(T, U) = __traits(compiles, { cast(U)(T.init); });
77 
78 enum isIndex(I) = (is(I == enum) ||
79 				   isUnsigned!I || /+ TODO: should we allow isUnsigned here? +/
80 				   isCastableTo!(I, size_t));
81 
82 /** Check if `T` can be indexed by an instance of `I`.
83  *
84  * See_Also: http://forum.dlang.org/post/ajxtksnsxqmeulsedmae@forum.dlang.org
85  *
86  * TODO: move to traits_ex.d
87  * TODO: Move to Phobos
88  */
89 enum hasIndexing(T, I = size_t) = is(typeof(T.init[I.init]) == typeof(T.init[0]));
90 
91 ///
92 pure nothrow @safe @nogc unittest {
93 	static assert(!hasIndexing!(int));
94 	static assert(hasIndexing!(int[3]));
95 	static assert(hasIndexing!(byte[]));
96 	static assert(hasIndexing!(byte[], uint));
97 	static assert(hasIndexing!(string));
98 }
99 
100 /** Check if `R` is indexable by `I`. */
101 enum isIndexableBy(R, I) = (hasIndexing!R && isIndex!I);
102 
103 ///
104 pure nothrow @safe @nogc unittest {
105 	static assert(isIndexableBy!(int[3], ubyte));
106 }
107 
108 /** Check if `R` is indexable by a automatically `R`-local defined integer type named `I`.
109  */
110 enum isIndexableBy(R, alias I) = (hasIndexing!R && is(string == typeof(I))); /+ TODO: extend to isSomeString? +/
111 
112 ///
113 pure nothrow @safe @nogc unittest {
114 	static assert(isIndexableBy!(int[], "I"));
115 }
116 
117 /** Generate bounds-checked `opIndex` and `opSlice`.
118  */
119 static private
120 mixin template _genIndexAndSliceOps(I)
121 {
122 	import nxt.conv_ex : toDefaulted;
123 
124 	// indexing
125 
126 	/// Get element at compile-time index `i`.
127 	auto ref at(size_t i)() inout
128 	{
129 		assert(cast(size_t)i < _r.length, "Index " ~ i ~ " must be smaller than array length" ~ _r.length.stringof);
130 		return _r[i];
131 	}
132 
133 	/// Get element at index `i`.
134 	auto ref opIndex(I i) inout
135 	{
136 		assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof);
137 		return _r[cast(size_t)i];
138 	}
139 
140 	/// Set element at index `i` to `value`.
141 	auto ref opIndexAssign(V)(V value, I i)
142 	{
143 		assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof);
144 
145 		import core.lifetime : move;
146 
147 		move(value, _r[cast(size_t)i]);
148 		return _r[cast(size_t)i];
149 	}
150 
151 	// slicing
152 	static if (hasSlicing!R)
153 	{
154 		auto ref opSlice(I i, I j) inout
155 		{
156 			return _r[cast(size_t)i .. cast(size_t)j];
157 		}
158 		auto ref opSliceAssign(V)(V value, I i, I j)
159 		{
160 			return _r[cast(size_t)i .. cast(size_t)j] = value; /+ TODO: use `move()` +/
161 		}
162 	}
163 }
164 
165 /** Generate @trusted non-bounds-checked `opIndex` and `opSlice`.
166  */
167 static private
168 mixin template _genIndexAndSliceOps_unchecked(I)
169 {
170 	@trusted:
171 
172 	// indexing
173 
174 	/// Get element at compile-time index `i`.
175 	auto ref at(size_t i)() inout
176 	{
177 		static assert(i < _r.length, "Index " ~ i.stringof ~ " must be smaller than array length " ~ _r.length.stringof);
178 		return _r.ptr[i];
179 	}
180 
181 	/// Get element at index `i`.
182 	auto ref opIndex(I i) inout
183 	{
184 		return _r.ptr[cast(size_t)i]; // safe to avoid range checking
185 	}
186 
187 	/// Set element at index `i` to `value`.
188 		auto ref opIndexAssign(V)(V value, I i)
189 	{
190 		return _r.ptr[cast(size_t)i] = value;
191 	}
192 
193 	// slicing
194 	static if (hasSlicing!R)
195 	{
196 		auto ref opSlice(I i, I j) inout			 { return _r.ptr[cast(size_t)i ..
197 																	 cast(size_t)j]; }
198 		auto ref opSliceAssign(V)(V value, I i, I j) { return _r.ptr[cast(size_t)i ..
199 																	 cast(size_t)j] = value; }
200 	}
201 }
202 
203 /** Wrapper for `R` with Type-Safe `I`-Indexing.
204 	See_Also: http://forum.dlang.org/thread/gayfjaslyairnzrygbvh@forum.dlang.org#post-gayfjaslyairnzrygbvh:40forum.dlang.org
205 
206 	TODO: Merge with https://github.com/rcorre/enumap
207 
208 	TODO: Use std.range.indexed when I is an enum with non-contigious
209 	enumerators. Perhaps use among aswell.
210 
211 	TODO: Rename to something more concise such as [Bb]y.
212 
213 	TODO: Allow `I` to be a string and if so derive `Index` to be that string.
214    */
215 struct IndexedBy(R, I)
216 if (isIndexableBy!(R, I))
217 {
218 	alias Index = I;		/// indexing type
219 	mixin _genIndexAndSliceOps!I;
220 	R _r;
221 	alias _r this; /+ TODO: Use opDispatch instead; to override only opSlice and opIndex +/
222 }
223 
224 /** Statically-Sized Array of ElementType `E` indexed by `I`.
225 	TODO: assert that `I` is continuous if it is a `enum`.
226 */
227 struct IndexedArray(E, I)
228 if (isIndex!I)
229 {
230 	static assert(I.min == 0, "Index type I is currently limited to start at 0 and be continuous");
231 	alias Index = I;			/// indexing type
232 	mixin _genIndexAndSliceOps!I;
233 	alias R = E[I.max + 1];	 // needed by mixins
234 	R _r;					   // static array
235 	alias _r this; /+ TODO: Use opDispatch instead; to override only opSlice and opIndex +/
236 }
237 
238 ///
239 pure nothrow @safe unittest {
240 	enum N = 7;
241 	enum I { x = 0, y = 1, z = 2}
242 	alias E = int;
243 	alias A = IndexedArray!(E, I);
244 	static assert(A.sizeof == 3*int.sizeof);
245 	A x;
246 	x[I.x] = 1;
247 	x[I.y] = 2;
248 	x[I.z] = 3;
249 	static assert(!__traits(compiles, { x[1] = 3; })); // no integer indexing
250 }
251 
252 /** Instantiator for `IndexedBy`.
253  */
254 auto indexedBy(I, R)(R range)
255 if (isIndexableBy!(R, I))
256 {
257 	return IndexedBy!(R, I)(range);
258 }
259 
260 struct IndexedBy(R, string IndexTypeName)
261 if (hasIndexing!R &&
262 	IndexTypeName != "IndexTypeName") // prevent name lookup failure
263 {
264 	static if (__traits(isStaticArray, R)) // if length is known at compile-time
265 	{
266 		import nxt.modulo : Mod;
267 		mixin(`alias ` ~ IndexTypeName ~ ` = Mod!(R.length);`); /+ TODO: relax integer precision argument of `Mod` +/
268 
269 		// dummy variable needed for symbol argument to `_genIndexAndSliceOps_unchecked`
270 		mixin(`private static alias I__ = ` ~ IndexTypeName ~ `;`);
271 
272 		mixin _genIndexAndSliceOps_unchecked!(I__); // no range checking needed because I is always < R.length
273 
274 		/** Get index of element `E` wrapped in a `bool`-convertable struct. */
275 		auto findIndex(E)(E e) pure nothrow @safe @nogc
276 		{
277 			static struct Result
278 			{
279 				Index index;	// index if exists is `true', 0 otherwise
280 				bool exists;  // `true` iff `index` is defined, `false` otherwise
281 				bool opCast(T : bool)() const pure nothrow @safe @nogc { return exists; }
282 			}
283 			import std.algorithm : countUntil;
284 			const ix = _r[].countUntil(e); // is safe
285 			if (ix >= 0)
286 			{
287 				return Result(Index(ix), true);
288 			}
289 			return Result(Index(0), false);
290 		}
291 	}
292 	else
293 	{
294 		mixin(q{ struct } ~ IndexTypeName ~
295 			  q{ {
296 					  alias T = size_t;
297 					  this(T ix) { this._ix = ix; }
298 					  T opCast(U : T)() const { return _ix; }
299 					  private T _ix = 0;
300 				  }
301 			  });
302 		mixin _genIndexAndSliceOps!(mixin(IndexTypeName));
303 	}
304 	R _r;
305 	alias _r this; /+ TODO: Use opDispatch instead; to override only opSlice and opIndex +/
306 }
307 
308 /* Wrapper type for `R' indexable/sliceable only with type `R.Index`. */
309 template TypesafelyIndexed(R)
310 if (hasIndexing!R) // prevent name lookup failure
311 {
312 	alias TypesafelyIndexed = IndexedBy!(R, "Index");
313 }
314 
315 /** Instantiator for `IndexedBy`.
316  */
317 auto indexedBy(string I, R)(R range)
318 if (isArray!R &&
319 	I != "IndexTypeName") // prevent name lookup failure
320 {
321 	return IndexedBy!(R, I)(range);
322 }
323 
324 /** Instantiator for `TypesafelyIndexed`.
325  */
326 auto strictlyIndexed(R)(R range)
327 if (hasIndexing!(R))
328 {
329 	return TypesafelyIndexed!(R)(range);
330 }
331 
332 ///
333 pure nothrow @safe unittest {
334 	enum m = 3;
335 	int[m] x = [11, 22, 33];
336 	auto y = x.strictlyIndexed;
337 
338 	alias Y = typeof(y);
339 
340 	static assert(is(typeof(y.findIndex(11).index) == Y.Index));
341 
342 	assert(y.findIndex(11).exists);
343 	assert(y.findIndex(22).exists);
344 	assert(y.findIndex(33).exists);
345 
346 	if (auto hit = y.findIndex(11)) { assert(hit.index == 0); } else { assert(false); }
347 	if (auto hit = y.findIndex(22)) { assert(hit.index == 1); } else { assert(false); }
348 	if (auto hit = y.findIndex(33)) { assert(hit.index == 2); } else { assert(false); }
349 
350 	assert(!y.findIndex(44));
351 	assert(!y.findIndex(55));
352 
353 	assert(!y.findIndex(44).exists);
354 	assert(!y.findIndex(55).exists);
355 }
356 
357 ///
358 pure nothrow @safe unittest {
359 	enum N = 7;
360 	alias T = TypesafelyIndexed!(size_t[N]); // static array
361 	static assert(T.sizeof == N*size_t.sizeof);
362 	import nxt.modulo : Mod, mod;
363 
364 	T x;
365 
366 	x[Mod!N(1)] = 11;
367 	x[1.mod!N] = 11;
368 	assert(x[1.mod!N] == 11);
369 
370 	x.at!1 = 12;
371 	static assert(!__traits(compiles, { x.at!N; }));
372 	assert(x.at!1 == 12);
373 }
374 
375 ///
376 pure nothrow @safe unittest {
377 	int[3] x = [1, 2, 3];
378 
379 	// sample index
380 	struct Index(T = size_t)
381 		if (isUnsigned!T)
382 	{
383 		this(T i) { this._i = i; }
384 		T opCast(U : T)() const { return _i; }
385 		private T _i = 0;
386 	}
387 	alias J = Index!size_t;
388 
389 	enum E { e0, e1, e2 }
390 
391 	with (E)
392 	{
393 		auto xb = x.indexedBy!ubyte;
394 		auto xi = x.indexedBy!uint;
395 		auto xj = x.indexedBy!J;
396 		auto xe = x.indexedBy!E;
397 		auto xf = x.strictlyIndexed;
398 
399 		auto xs = x.indexedBy!"I";
400 		alias XS = typeof(xs);
401 		XS xs_;
402 
403 		// indexing with correct type
404 		xb[  0 ] = 11; assert(xb[  0 ] == 11);
405 		xi[  0 ] = 11; assert(xi[  0 ] == 11);
406 		xj[J(0)] = 11; assert(xj[J(0)] == 11);
407 		xe[ e0 ] = 11; assert(xe[ e0 ] == 11);
408 
409 		// indexing with wrong type
410 		static assert(!__traits(compiles, { xb[J(0)] = 11; }));
411 		static assert(!__traits(compiles, { xi[J(0)] = 11; }));
412 		static assert(!__traits(compiles, { xj[  0 ] = 11; }));
413 		static assert(!__traits(compiles, { xe[  0 ] = 11; }));
414 		static assert(!__traits(compiles, { xs[  0 ] = 11; }));
415 		static assert(!__traits(compiles, { xs_[  0 ] = 11; }));
416 
417 		import std.algorithm.comparison : equal;
418 		import std.algorithm.iteration : filter;
419 
420 		assert(equal(xb[].filter!(a => a < 11), [2, 3]));
421 		assert(equal(xi[].filter!(a => a < 11), [2, 3]));
422 		assert(equal(xj[].filter!(a => a < 11), [2, 3]));
423 		assert(equal(xe[].filter!(a => a < 11), [2, 3]));
424 		// assert(equal(xs[].filter!(a => a < 11), [2, 3]));
425 	}
426 }
427 
428 ///
429 pure nothrow @safe unittest {
430 	auto x = [1, 2, 3];
431 
432 	// sample index
433 	struct Index(T = size_t)
434 		if (isUnsigned!T)
435 	{
436 		this(T ix) { this._ix = ix; }
437 		T opCast(U : T)() const { return _ix; }
438 		private T _ix = 0;
439 	}
440 	alias J = Index!size_t;
441 
442 	enum E { e0, e1, e2 }
443 
444 	with (E)
445 	{
446 		auto xb = x.indexedBy!ubyte;
447 		auto xi = x.indexedBy!uint;
448 		auto xj = x.indexedBy!J;
449 		auto xe = x.indexedBy!E;
450 
451 		// indexing with correct type
452 		xb[  0 ] = 11; assert(xb[  0 ] == 11);
453 		xi[  0 ] = 11; assert(xi[  0 ] == 11);
454 		xj[J(0)] = 11; assert(xj[J(0)] == 11);
455 		xe[ e0 ] = 11; assert(xe[ e0 ] == 11);
456 
457 		// slicing with correct type
458 		xb[  0  ..   1 ] = 12; assert(xb[  0  ..   1 ] == [12]);
459 		xi[  0  ..   1 ] = 12; assert(xi[  0  ..   1 ] == [12]);
460 		xj[J(0) .. J(1)] = 12; assert(xj[J(0) .. J(1)] == [12]);
461 		xe[ e0  ..  e1 ] = 12; assert(xe[ e0  ..  e1 ] == [12]);
462 
463 		// indexing with wrong type
464 		static assert(!__traits(compiles, { xb[J(0)] = 11; }));
465 		static assert(!__traits(compiles, { xi[J(0)] = 11; }));
466 		static assert(!__traits(compiles, { xj[  0 ] = 11; }));
467 		static assert(!__traits(compiles, { xe[  0 ] = 11; }));
468 
469 		// slicing with wrong type
470 		static assert(!__traits(compiles, { xb[J(0) .. J(0)] = 11; }));
471 		static assert(!__traits(compiles, { xi[J(0) .. J(0)] = 11; }));
472 		static assert(!__traits(compiles, { xj[  0  ..   0 ] = 11; }));
473 		static assert(!__traits(compiles, { xe[  0  ..   0 ] = 11; }));
474 
475 		import std.algorithm.comparison : equal;
476 		import std.algorithm.iteration : filter;
477 
478 		assert(equal(xb.filter!(a => a < 11), [2, 3]));
479 		assert(equal(xi.filter!(a => a < 11), [2, 3]));
480 		assert(equal(xj.filter!(a => a < 11), [2, 3]));
481 		assert(equal(xe.filter!(a => a < 11), [2, 3]));
482 	}
483 }
484 
485 ///
486 pure nothrow @safe unittest {
487 	auto x = [1, 2, 3];
488 	struct I(T = size_t)
489 	{
490 		this(T ix) { this._ix = ix; }
491 		T opCast(U : T)() const { return _ix; }
492 		private T _ix = 0;
493 	}
494 	alias J = I!size_t;
495 	auto xj = x.indexedBy!J;
496 }
497 
498 ///
499 pure nothrow @safe unittest {
500 	auto x = [1, 2, 3];
501 	struct I(T = size_t)
502 	{
503 		private T _ix = 0;
504 	}
505 	alias J = I!size_t;
506 	static assert(!__traits(compiles, { auto xj = x.indexedBy!J; }));
507 }
508 
509 ///
510 version (none)
511 pure nothrow @safe unittest {
512 	auto x = [1, 2, 3];
513 	import nxt.bound : Bound;
514 	alias B = Bound!(ubyte, 0, 2);
515 	B b;
516 	auto c = cast(size_t)b;
517 	auto y = x.indexedBy!B;
518 }
519 
520 ///
521 pure nothrow @safe unittest {
522 	import nxt.container.dynamic_array : Array = DynamicArray;
523 
524 	enum Lang { en, sv, fr }
525 
526 	alias Ixs = Array!int;
527 
528 	struct S {
529 		Lang lang;
530 		string data;
531 		Ixs ixs;
532 	}
533 
534 	alias A = Array!S;
535 
536 	struct I {
537 		size_t opCast(U : size_t)() const pure nothrow @safe @nogc { return _ix; }
538 		uint _ix;
539 		alias _ix this;
540 	}
541 
542 	I i;
543 	static assert(isCastableTo!(I, size_t));
544 	static assert(isIndexableBy!(A, I));
545 
546 	alias IA = IndexedBy!(A, I);
547 	IA ia;
548 	ia ~= S.init;
549 	assert(ia.length == 1);
550 	Ixs ixs;
551 	ixs.length = 42;
552 	import std.algorithm.mutation : move;
553 	auto s = S(Lang.en, "alpha", move(ixs)); /+ TODO: use generic `makeOfLength` +/
554 
555 	import core.lifetime : move;
556 
557 	ia ~= move(s);
558 	assert(ia.length == 2);
559 }
560 
561 /** Returns: a `string` containing the definition of an `enum` named `name` and
562 	with enumerator names given by `Es`, optionally prepended with `prefix` and
563 	appended with `suffix`.
564 
565 	TODO: Move to Phobos std.typecons
566 */
567 template makeEnumFromSymbolNames(string prefix = `__`,
568 								 string suffix = ``,
569 								 bool firstUndefined = true,
570 								 bool useMangleOf = false,
571 								 Es...)
572 if (Es.length != 0)
573 {
574 	enum members =
575 	{
576 		string s = firstUndefined ? `undefined, ` : ``;
577 		foreach (E; Es)
578 		{
579 			static if (useMangleOf)
580 			{
581 				enum E_ = E.mangleof;
582 			}
583 			else
584 			{
585 				import std.traits : isPointer;
586 				static if (isPointer!E)
587 				{
588 					import std.traits : TemplateOf;
589 					enum isTemplateInstance = is(typeof(TemplateOf!(typeof(*E.init))));
590 					static if (isTemplateInstance) // strip template params for now
591 					{
592 						enum E_ = __traits(identifier, TemplateOf!(typeof(*E))) ~ `Ptr`;
593 					}
594 					else
595 					{
596 						enum E_ = typeof(*E.init).stringof ~ `Ptr`;
597 					}
598 				}
599 				else
600 				{
601 					enum E_ = E.stringof;
602 				}
603 
604 			}
605 			s ~= prefix ~ E_ ~ suffix ~ `, `;
606 		}
607 		return s;
608 	}();
609 	mixin("enum makeEnumFromSymbolNames : ubyte {" ~ members ~ "}");
610 }
611 
612 ///
613 pure nothrow @safe @nogc unittest {
614 	import std.meta : AliasSeq;
615 	struct E(T) { T x; }
616 	alias Types = AliasSeq!(byte, short, int*, E!int*);
617 	alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, false, Types);
618 	static assert(is(Type == enum));
619 	static assert(Type.undefined.stringof == `undefined`);
620 	static assert(Type._byte_.stringof == `_byte_`);
621 	static assert(Type._short_.stringof == `_short_`);
622 	static assert(Type._intPtr_.stringof == `_intPtr_`);
623 	static assert(Type._EPtr_.stringof == `_EPtr_`);
624 }
625 
626 ///
627 pure nothrow @safe @nogc unittest {
628 	import std.meta : AliasSeq;
629 
630 	struct E(T) { T x; }
631 
632 	alias Types = AliasSeq!(byte, short, int*, E!int*);
633 	alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, true, Types);
634 
635 	static assert(is(Type == enum));
636 
637 	static assert(Type.undefined.stringof == `undefined`);
638 	static assert(Type._g_.stringof == `_g_`);
639 	static assert(Type._s_.stringof == `_s_`);
640 	static assert(Type._Pi_.stringof == `_Pi_`);
641 }
642 
643 /**
644    See_Also: https://p0nce.github.io/d-idioms/#Rvalue-references:-Understanding-auto-ref-and-then-not-using-it
645    */
646 mixin template RvalueRef()
647 {
648 	alias T = typeof(this); // typeof(this) get us the type we're in
649 	static assert (is(T == struct));
650 
651 	@nogc @safe
652 	ref inout(T) asRef() inout pure nothrow return
653 	{
654 		return this;
655 	}
656 }
657 
658 @safe @nogc pure nothrow unittest {
659 	static struct Vec
660 	{
661 		@safe @nogc pure nothrow:
662 		float x, y;
663 		this(float x, float y) pure nothrow
664 		{
665 			this.x = x;
666 			this.y = y;
667 		}
668 		mixin RvalueRef;
669 	}
670 
671 	static void foo(ref const Vec pos)
672 	{
673 	}
674 
675 	Vec v = Vec(42, 23);
676 	foo(v);					 // works
677 	foo(Vec(42, 23).asRef);	 // works as well, and use the same function
678 }
679 
680 /** Convert enum value `v` to `string`.
681 	See_Also: http://forum.dlang.org/post/aqqhlbaepoimpopvouwv@forum.dlang.org
682  */
683 string enumToString(E)(E v)
684 {
685 	static assert(is(E == enum), "emumToString is only meant for enums");
686 	switch(v)
687 	{
688 		foreach (m; __traits(allMembers, E))
689 		{
690 			case mixin("E." ~ m) : return m;
691 		}
692 		default:
693 		{
694 			string result = "cast(" ~ E.stringof ~ ")";
695 			uint val = v;
696 
697 			enum headLength = E.stringof.length + "cast()".length;
698 			uint log10Val = (val < 10) ? 0 : (val < 100) ? 1 : (val < 1_000) ? 2 :
699 				(val < 10_000) ? 3 : (val < 100_000) ? 4 : (val < 1_000_000) ? 5 :
700 				(val < 10_000_000) ? 6 : (val < 100_000_000) ? 7 : (val < 1000_000_000) ? 8 : 9;
701 
702 			result.length += log10Val + 1;
703 
704 			foreach (uint i; 0 .. log10Val + 1)
705 			{
706 				cast(char)result[headLength + log10Val - i] = cast(char) ('0' + (val % 10));
707 				val /= 10;
708 			}
709 			return cast(string) result;
710 		}
711 	}
712 }
713 
714 pure nothrow @safe unittest {
715 	enum ET { one, two }
716 	// static assert(to!string(ET.one) == "one");
717 	static assert (enumToString(ET.one) == "one");
718 	assert (enumToString(ET.one) == "one");
719 }