[Issue 24892] We need a __traits trait to test for whether one class is derived from another
d-bugmail at puremagic.com
d-bugmail at puremagic.com
Fri Dec 6 09:58:50 UTC 2024
https://issues.dlang.org/show_bug.cgi?id=24892
--- Comment #3 from Jonathan M Davis <issues.dlang at jmdavisProg.com> ---
(In reply to Dennis from comment #2)
> Usually when a template function accepts a generic struct/class, the
> constraints test for capabilities (like having a certain property or
> operator overload) that the function wants to use on a parameter of that
> type.
>
> Implicitly converting is an operation that you might want to do. Being
> derived from a specific class is not an operation on its own. If you do
> something that only works with derived classes, like array conversion based
> on covariance, you can test for that specifically:
>
> if(is(const(T)[] : const(U)[])
>
> That would also makes it clear *why* you specifically allow derived classes
> from `U` but not classes that convert to `U`.
>
> I'm really interested in seeing the function where you want to use this
> trait, because I suspect it's trying to solve a problem that should be
> solved elsewhere.
1. Determining whether a class is derived from another matters for type
introspection in general and not just for template constraints. For instance,
if you were trying to wrap D types for an interpreted language implemented in
D, you might need to know the class hierarchy so that it could be replicated in
that language. And a static if might need to test for whether a class was
derived from a specific class in order to do something specific with it that
has nothing to do with duck typing (e.g. it could affect which functions you
wanted to compile into a templated type).
2. I would actually argue that testing for implicit conversions with template
constraints is almost always a mistake. Occasionally, it makes sense, but in
general, it causes bugs. A template constraint which tests for an implicit
conversion does not actually cause that implicit conversion to happen, which
means that unless the templated function's implementation forces the
conversion, the type that passed the constraint may or may not even work with
the function (best case, you get an error; worst case, you get silent breakage
due to the type that the template was instantiated with not actually acting
like the type that it would implicitly convert to - or you could get incorrect
behavior because the implicit conversion ends up happening in multiple places
throughout the function, but the original object is still the one being passed
around). So, testing for an implicit conversion and not actually forcing it is
just begging for trouble.
And to make matters worse, even if you do force the conversion, that can cause
`@safe`ty problems in some cases. isConvertibleToString is a prime example of
this. It tests whether a type implicit converts to string, but of course, it
doesn't cause the conversion to occur at the call site. So, if a static array
of characters is passed in, the template will be instantiated with the static
array type and not the dynamic array type, so the static array will be copied
into the function. And then if the implicit conversion is done within the
function, it will be slicing the local static array, not the static array at
the call site. In some cases, this works, but if the dynamic array escapes,
then it will end up referring to invalid memory - and this would be invisible
barring the usage of scope with DIP 1000. Other implicit conversions could have
similar problems. So, in general, the implicit conversion needs to occur at the
call site (like it does when a function explicitly takes a dynamic array, and
you pass it a static array), but you can't actually force that with templates.
So, what needs to happen in the general case is that you simply don't allow the
implicit conversion with the template constraint and instead require that the
caller do the conversion themselves to ensure that it's done at the call site -
just like you have to do right now to pass a static array to a range-based
function.
As such, I would argue that it's a bad idea to be testing for whether a class
implicitly converts to a base class with a template constraint. It's likely to
be far less error-prone than most implicit conversions given that (assuming
that alias this is not involved), worst case, you're using a reference that's
typed as the derived class and not the base class, which likely won't cause
issues. However, if alias this is involved, who knows what's going to happen.
The type being passed in could even be a struct rather than a class and end up
behaving quite differently from the class reference, particularly if the
implicit conversion allocates a new class object, since then you could end up
passing the struct around within the function and then allocating a new
instance of the class every time the code tries to call a function that's on
the class but not on the struct. And you could get similarly weird behavior if
it's an alias this on a class instead. It really depends on the actual
implementation.
So, IMHO, in general, template constraints that care whether a class is derived
from another should actually be testing that it's derived and not testing for
an implicit conversion. And if the API is all that matters, then that's what
should be tested for and not an implicit conversion.
So, in the general case, I think that it's a mistake to be testing for
inheritance via implicit conversion, and I expect that the main reasons that it
hasn't been a bigger issue is because alias this fortunately isn't used all
that often and because if a function is supposed to take a particular class
type, it will probably just take that class type explicitly rather than be
templated on the argument type.
But even if there isn't agreement on the idea that testing for implicit
conversions with template constraints is almost always a mistake, IMHO, this
kind of information is basic information about a type, and it can matter for
type introspection outside of template constraints. So, it should be queryable,
and the fact that we don't actually have a way to test whether a class is
derived from another is a problem.
--
More information about the Digitalmars-d-bugs
mailing list