1 /** Traits used by containers.
2  *
3  * TODO add `isUnorderedContainer` and `isUnorderedRange` traits and used to
4  * forbid hash algorithms to operate on unordered containers (such as
5  * `open_hashmap` and `open_hashmap`) and their ranges.
6  */
7 module nxt.container_traits;
8 
9 public import nxt.gc_traits;
10 
11 @safe:
12 
13 /** True if a `T` needs to be passed by move instead of value.
14  *
15  * See_Also:
16  */
17 template needsMove(T)
18 {
19     import core.internal.traits : hasElaborateDestructor;
20     enum needsMove = hasElaborateDestructor!T || !__traits(isCopyable, T); // TODO is this ok?
21 }
22 
23 // TODO this can be simplified for faster compilation
24 template ContainerElementType(ContainerType,
25                               ElementType)
26 {
27     import std.traits : isMutable, hasIndirections, PointerTarget, isPointer,
28         Unqual;
29 
30     template ET(bool isConst, T)
31     {
32         static if (isPointer!ElementType)
33         {
34             enum PointerIsConst = is(ElementType == const);
35             enum PointerIsImmutable = is(ElementType == immutable);
36             enum DataIsConst = is(PointerTarget!ElementType == const);
37             enum DataIsImmutable = is(PointerTarget!ElementType == immutable);
38             static if (isConst)
39             {
40                 static if (PointerIsConst)
41                 {
42                     alias ET = ElementType;
43                 }
44                 else static if (PointerIsImmutable)
45                 {
46                     alias ET = ElementType;
47                 }
48                 else
49                 {
50                     alias ET = const(PointerTarget!ElementType)*;
51                 }
52             }
53             else
54             {
55                 static assert(DataIsImmutable,
56                               "An immutable container cannot reference const or mutable data");
57                 static if (PointerIsConst)
58                 {
59                     alias ET = immutable(PointerTarget!ElementType)*;
60                 }
61                 else
62                 {
63                     alias ET = ElementType;
64                 }
65             }
66         }
67         else
68         {
69             static if (isConst)
70             {
71                 static if (is(ElementType == immutable))
72                 {
73                     alias ET = ElementType;
74                 }
75                 else
76                 {
77                     alias ET = const(Unqual!ElementType);
78                 }
79             }
80             else
81             {
82                 alias ET = immutable(Unqual!ElementType);
83             }
84         }
85     }
86 
87     static if (isMutable!ContainerType)
88     {
89         alias ContainerElementType = ElementType;
90     }
91     else
92     {
93         static if (hasIndirections!ElementType)
94         {
95             alias ContainerElementType = ET!(is(ContainerType == const), ElementType);
96         }
97         else
98         {
99             alias ContainerElementType = ElementType;
100         }
101     }
102 }
103 
104 /// Returns: `true` iff `T` is a template instance, `false` otherwise.
105 private template isTemplateInstance(T)
106 {
107     import std.traits : TemplateOf;
108     enum isTemplateInstance = is(typeof(TemplateOf!(T)));
109 }
110 
111 /** Is `true` iff `T` can be put in a hashset or hashmap. */
112 template isHashable(T)
113 {
114     import std.traits : hasAliasing;
115     enum isHashable = !hasAliasing!T;
116 }
117 
118 @safe pure unittest
119 {
120     static assert(isHashable!int);
121     static assert(isHashable!string);
122     static assert(!isHashable!(char[]));
123     static assert(!isHashable!(const(char)[]));
124 }
125 
126 /** Is `true` iff `T` is a set like container. */
127 template isSet(T)
128 {
129     import std.range.primitives : hasLength;
130     enum isSet = (__traits(hasMember, T, "insert") && // TODO assert O(1)
131                   __traits(hasMember, T, "remove") && // TODO assert O(1)
132                   __traits(compiles, { auto _ = T.init.byElement; }));
133 }
134 
135 /** Is `true` iff `T` is a set like container with elements of type `E`. */
136 template isSetOf(T, E)
137 {
138     import std.range.primitives : hasLength;
139     enum isSetOf = (is(typeof(T.init.insert(E.init))) && // TODO assert O(1)
140                     is(typeof(T.init.remove(E.init))) && // TODO assert O(1)
141                     __traits(compiles, { auto _ = T.init.byElement; }));
142 }
143 
144 /** Allocate an array of `T`-elements of length `length` using `Allocator`.
145  */
146 T[] makeInitZeroArray(T, alias Allocator)(const size_t length) @trusted
147 {
148     version(none)               // TODO activate
149     {
150         // See: https://github.com/dlang/phobos/pull/6411
151         import std.experimental.allocator.gc_allocator : GCAllocator;
152         static if (__traits(hasMember, GCAllocator, "allocateZeroed"))
153         {
154             static assert(0, "Use std.experimental.allocator.package.make!(T) instead because it makes use of allocateZeroed.");
155         }
156     }
157     immutable byteCount = T.sizeof * length;
158     /* when possible prefer call to calloc before malloc+memset:
159      * https://stackoverflow.com/questions/2688466/why-mallocmemset-is-slower-than-calloc */
160     static if (__traits(hasMember, Allocator, "allocateZeroed"))
161     {
162         pragma(inline, true);
163         return cast(typeof(return))Allocator.allocateZeroed(byteCount);
164     }
165     else
166     {
167         auto array = cast(typeof(return))Allocator.allocate(byteCount);
168         import core.stdc.string : memset;
169         memset(array.ptr, 0, byteCount);
170         return array;
171     }
172 }
173 
174 /** Variant of `hasElaborateDestructor` that also checks for destructor when `S`
175  * is a `class`.
176  *
177  * See_Also: https://github.com/dlang/phobos/pull/4119
178  */
179 template hasElaborateDestructorNew(S)
180 {
181     static if (is(S == struct) ||
182                is(S == class)) // check also class
183     {
184         static if (__traits(hasMember, S, "__dtor"))
185         {
186             enum bool hasElaborateDestructorNew = true;
187         }
188         else
189         {
190             import std.traits : FieldTypeTuple;
191             import std.meta : anySatisfy;
192             enum hasElaborateDestructorNew = anySatisfy!(.hasElaborateDestructorNew, FieldTypeTuple!S);
193         }
194     }
195     else
196     {
197         import std.traits : isStaticArray;
198         static if (isStaticArray!S && S.length)
199         {
200             enum bool hasElaborateDestructorNew = hasElaborateDestructorNew!(typeof(S.init[0]));
201         }
202         else
203         {
204             enum bool hasElaborateDestructorNew = false;
205         }
206     }
207 }
208 
209 /** Is `true` iff `T` is repesented as a memory address. */
210 template isAddress(T)
211 {
212     static if (is(T == class))
213     {
214         enum isAddress = true;  // a class is memory-wise just a pointer
215     }
216     else
217     {
218         import std.traits : isPointer;
219         enum isAddress = isPointer!T;
220     }
221 }
222 
223 ///
224 @safe pure nothrow @nogc unittest
225 {
226     static assert( isAddress!(int*));
227     static assert(!isAddress!(int));
228 
229     class C {}
230     static assert( isAddress!(C));
231 
232     struct S {}
233     static assert(!isAddress!(S));
234     static assert( isAddress!(S*));
235 }