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 }