|
| 1 | +isSomeString and isNarrowString are now `false` for enums |
| 2 | + |
| 3 | +Previously, enums whose base type was a string type were `true` for |
| 4 | +$(REF isSomeString, std, traits) and $(REF isNarrowString, std, traits). |
| 5 | +Occasionally, this was useful, but in general, it was a source of bugs, because |
| 6 | +code that works with strings does not necessarily work with enums whose base |
| 7 | +type is a string, making it easy to write code where an enum would pass the |
| 8 | +template constraint and then the template would fail to compile. For instance, |
| 9 | +enums of base type string are `false` for |
| 10 | +$(REF `isInputRange`, std, range, primitives). As such, it really doesn't make |
| 11 | +sense for $(REF isSomeString, std, traits) and |
| 12 | +$(REF isNarrowString, std, traits) to be `true` for enums. |
| 13 | + |
| 14 | +For some code, this will be a breaking change, but most code will either be |
| 15 | +unaffected, or it will then fail with enums at the template constraint instead |
| 16 | +of inside the function. So, the risk of code breakage is minimal but does exist. |
| 17 | +Other code will now be able to remove stuff like `!is(T == enum)` from its |
| 18 | +template constraints, since that is now part of |
| 19 | +$(REF isSomeString, std, traits) and $(REF isNarrowString, std, traits), but |
| 20 | +it will continue to compile with the now unnecessary check for enums. |
| 21 | + |
| 22 | +Code that uses $(REF isSomeString, std, traits) or |
| 23 | +$(REF isNarrowString, std, traits) in a template constraint and has no other |
| 24 | +conditions which would prevent enums from passing the constraint but then would |
| 25 | +fail to compile inside the template if an enum were passed to it will now fail |
| 26 | +to compile at the template constraint instead of inside the template. So, such |
| 27 | +code is fixed rather than broken. |
| 28 | + |
| 29 | +The rare code that does break because of these changes is code that uses |
| 30 | +$(REF isSomeString, std, traits) or $(REF isNarrowString, std, traits) in a |
| 31 | +template constraint or static if and does not use other conditions to prevent enums |
| 32 | +from passing and actually has code in the template which compiles with both strings |
| 33 | +and enums with a base type of string. Such code will need to be changed to use |
| 34 | +$(REF OriginalType, std, traits), $(REF asOriginalType, std, conv), or |
| 35 | +$(REF StringTypeOf, std, traits) instead. e.g. for enums to pass the constraint, |
| 36 | +instead of |
| 37 | + |
| 38 | +--- |
| 39 | +auto foo(S)(S str) |
| 40 | +if (isSomeString!S) |
| 41 | +{ |
| 42 | + ... |
| 43 | +} |
| 44 | +--- |
| 45 | + |
| 46 | +the code would be |
| 47 | + |
| 48 | +--- |
| 49 | +auto foo(S)(S str) |
| 50 | +if (isSomeString!(OriginalType!S)) |
| 51 | +{ |
| 52 | + ... |
| 53 | +} |
| 54 | +--- |
| 55 | + |
| 56 | +As a rule of thumb, generic code should either disallow implicit conversions |
| 57 | +and force the caller to do the conversion explicitly (which generally is the |
| 58 | +least error-prone approach), or it should force the conversion to the desired |
| 59 | +type before the parameter is used in the function so that the function is |
| 60 | +definitely operating on the desired type and not just on one that implicitly |
| 61 | +converts to it and thus will work regardless of whether the argument was the |
| 62 | +exact type or implicitly converted to it. |
| 63 | + |
| 64 | +However, great care should be taken if the implicit conversion will result in |
| 65 | +slicing the parameter or otherwise referring to its address, since that makes |
| 66 | +it very easy for a reference to local memory to escape the function, whereas if |
| 67 | +the conversion is done at the call site, then the slicing is done at the call |
| 68 | +site, so the dynamic array is still valid when the function returns. |
| 69 | +Fortunately, enums with a base type of string do not have that problem, but |
| 70 | +other types which implicitly convert to dynamic arrays (such as static arrays) |
| 71 | +do have that problem, which is a big reason why it's generally recommended to |
| 72 | +not have generic functions accept types based on implicit conversions. |
| 73 | + |
| 74 | +It is recommended that if a function is supposed to accept both strings and |
| 75 | +types which implicitly convert to a string type, then it should explicitly |
| 76 | +accept dynamic arrays but be templated on the element type. e.g. |
| 77 | + |
| 78 | +--- |
| 79 | +auto foo(C)(C[] str) |
| 80 | +if (isSomeChar!C) |
| 81 | +{ |
| 82 | + ... |
| 83 | +} |
| 84 | +--- |
| 85 | + |
| 86 | +That way, any implicit conversions are done at the call site and do not |
| 87 | +introduce @safety problems inside the function. It also tends to result in |
| 88 | +template constraints which are shorter and more easily understood. |
0 commit comments