phobos/changelog/std-traits-issomestring.dd

88 lines
3.9 KiB
Text

isSomeString and isNarrowString are now `false` for enums
Previously, enums whose base type was a string type were `true` for
$(REF isSomeString, std, traits) and $(REF isNarrowString, std, traits).
Occasionally, this was useful, but in general, it was a source of bugs, because
code that works with strings does not necessarily work with enums whose base
type is a string, making it easy to write code where an enum would pass the
template constraint and then the template would fail to compile. For instance,
enums of base type string are `false` for
$(REF isInputRange, std, range, primitives). As such, it really doesn't make
sense for $(REF isSomeString, std, traits) and
$(REF isNarrowString, std, traits) to be `true` for enums.
For some code, this will be a breaking change, but most code will either be
unaffected, or it will then fail with enums at the template constraint instead
of inside the function. So, the risk of code breakage is minimal but does exist.
Other code will now be able to remove stuff like `!is(T == enum)` from its
template constraints, since that is now part of
$(REF isSomeString, std, traits) and $(REF isNarrowString, std, traits), but
it will continue to compile with the now unnecessary check for enums.
Code that uses $(REF isSomeString, std, traits) or
$(REF isNarrowString, std, traits) in a template constraint and has no other
conditions which would prevent enums from passing the constraint but then would
fail to compile inside the template if an enum were passed to it will now fail
to compile at the template constraint instead of inside the template. So, such
code is fixed rather than broken.
The rare code that does break because of these changes is code that uses
$(REF isSomeString, std, traits) or $(REF isNarrowString, std, traits) in a
template constraint or static if and does not use other conditions to prevent enums
from passing and actually has code in the template which compiles with both strings
and enums with a base type of string. Such code will need to be changed to use
$(REF OriginalType, std, traits), $(REF asOriginalType, std, conv), or
$(REF StringTypeOf, std, traits) instead. e.g. for enums to pass the constraint,
instead of
---
auto foo(S)(S str)
if (isSomeString!S)
{
...
}
---
the code would be
---
auto foo(S)(S str)
if (isSomeString!(OriginalType!S))
{
...
}
---
As a rule of thumb, generic code should either disallow implicit conversions
and force the caller to do the conversion explicitly (which generally is the
least error-prone approach), or it should force the conversion to the desired
type before the parameter is used in the function so that the function is
definitely operating on the desired type and not just on one that implicitly
converts to it and thus will work regardless of whether the argument was the
exact type or implicitly converted to it.
However, great care should be taken if the implicit conversion will result in
slicing the parameter or otherwise referring to its address, since that makes
it very easy for a reference to local memory to escape the function, whereas if
the conversion is done at the call site, then the slicing is done at the call
site, so the dynamic array is still valid when the function returns.
Fortunately, enums with a base type of string do not have that problem, but
other types which implicitly convert to dynamic arrays (such as static arrays)
do have that problem, which is a big reason why it's generally recommended to
not have generic functions accept types based on implicit conversions.
It is recommended that if a function is supposed to accept both strings and
types which implicitly convert to a string type, then it should explicitly
accept dynamic arrays but be templated on the element type. e.g.
---
auto foo(C)(C[] str)
if (isSomeChar!C)
{
...
}
---
That way, any implicit conversions are done at the call site and do not
introduce @safety problems inside the function. It also tends to result in
template constraints which are shorter and more easily understood.