1 /******************************************************************************* 2 3 Value comparison for structs and arbitrary types. 4 5 Does a deep equality comparison of one type to another. 6 7 'Deep' meaning: 8 * The _contents_ of dynamic arrays are compared 9 * Types are recursed, allowing multi-dimensional arrays to be compared 10 * All members of structs are compared (recursively, if needed). 11 12 Copyright: 13 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 14 All rights reserved. 15 16 License: 17 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 18 Alternatively, this file may be distributed under the terms of the Tango 19 3-Clause BSD License (see LICENSE_BSD.txt for details). 20 21 *******************************************************************************/ 22 23 module ocean.core.DeepCompare; 24 25 26 27 import ocean.meta.types.Qualifiers; 28 29 version (unittest) import ocean.core.Test; 30 31 /*************************************************************************** 32 33 Given a type T, returns true if the type contains a struct or 34 a floating-point type. 35 36 ***************************************************************************/ 37 38 private template needsSpecialCompare(T) 39 { 40 static if ( is(T V : V[]) ) 41 { 42 // T is an array. Strip off all of the [] to get 43 // the ultimate element type. 44 45 enum 46 { 47 needsSpecialCompare = needsSpecialCompare!(V) 48 }; 49 } 50 else 51 { 52 // It's special if it has NaN, or is a struct 53 enum 54 { 55 needsSpecialCompare = is ( typeof (T.nan) ) || 56 is ( T == struct ) 57 }; 58 } 59 } 60 61 /*************************************************************************** 62 63 Compares two values and returns true if they are equal. 64 65 This differs from built-in == in two respects. 66 67 1. Dynamic arrays are compared by value, even when they are struct members. 68 69 2. Floating point numbers which are NaN are considered equal. 70 This preserves the important property that deepEquals(x, x) is true 71 for all x. 72 73 Classes are compared in the normal way, using opEquals. 74 75 Params: 76 a = Struct to be compared with b 77 b = Struct to be compared with a 78 79 Returns: 80 true if equal 81 82 ***************************************************************************/ 83 84 85 public bool deepEquals(T)(T a, T b) 86 { 87 static if ( is (typeof(T.nan)) ) 88 { 89 //pragma(msg, "Comparing float: " ~ T.stringof); 90 91 // Deal with NaN. 92 // If x is NaN, then x == x is false 93 // So we return true if both a and b are NaN. 94 // (In D2, we'd just use "return a is b"). 95 96 return a == b || ( a != a && b != b); 97 } 98 else static if ( is ( T == struct) ) 99 { 100 //pragma(msg, "Comparing struct: " ~ T.stringof); 101 102 foreach(i, U; typeof(a.tupleof) ) 103 { 104 static if ( needsSpecialCompare!(U) ) 105 { 106 //pragma(msg, "Comparing special element " ~ U.stringof ); 107 108 // It is a special type (struct or float), 109 // or an array of special types 110 111 if ( !deepEquals(a.tupleof[i], b.tupleof[i]) ) 112 { 113 return false; 114 } 115 } 116 else 117 { 118 //pragma(msg, "\t not a special case: " ~ typeof(a.tupleof[i]).stringof); 119 120 if ( a.tupleof[i] != b.tupleof[i] ) 121 { 122 return false; 123 } 124 } 125 } 126 return true; 127 } 128 else static if ( is(T V : V[]) ) 129 { 130 // T is an array. 131 // If it is one of the special cases, we need to 132 // do an element-by-element compare. 133 134 static if ( needsSpecialCompare!(V) ) 135 { 136 //pragma(msg, "Comparing element-by-element of array " ~ T.stringof); 137 138 // Compare element-by-element. 139 140 if (a.length != b.length) 141 { 142 return false; 143 } 144 145 foreach ( j, m; a ) 146 { 147 if ( !deepEquals(m, b[j]) ) 148 return false; 149 } 150 151 return true; 152 } 153 else 154 { 155 //pragma(msg, "Simple array compare " ~ T.stringof); 156 157 // Not a special case, we can just use the builtin ==. 158 // Note that this works even for the multidimensional case. 159 160 return a == b; 161 } 162 } 163 else 164 { 165 //pragma(msg, "\t not a special case" ~ T.stringof); 166 return a == b; 167 } 168 } 169 170 171 unittest 172 { 173 struct S0 174 { 175 } 176 177 struct S1 178 { 179 int [] x; 180 } 181 182 struct S2 183 { 184 S1 [] y; 185 } 186 187 struct S3 188 { 189 S1 [][] z; 190 } 191 192 struct S4 193 { 194 double x; 195 } 196 197 struct S5 198 { 199 double[] x; 200 } 201 202 S0 a, b; 203 test(deepEquals(a, b)); 204 S1 a1, b1; 205 a1.x = [ 1, 2, 3]; 206 b1.x = [ 1, 2, 3]; 207 test(a1 !is b1); 208 test(deepEquals(a1, b1)); 209 test(a1.x == b1.x); 210 S2 a2, b2; 211 a2.y = [a1]; 212 b2.y = [b1]; 213 test(deepEquals(a2, b2)); 214 S3 a3, b3; 215 a3.z = [a2.y]; 216 b3.z = [b2.y]; 217 test(deepEquals(a3, b3)); 218 b3.z = [null]; 219 test(!deepEquals(a3, b3)); 220 S4 a4; 221 a4.x = double.nan; 222 test(deepEquals(a4, a4)); 223 S5 a5; 224 a5.x ~= double.nan; 225 test(deepEquals(a5, a5)); 226 }