https://issues.dlang.org/show_bug.cgi?id=24892
--- Comment #3 from Jonathan M Davis <[email protected]> --- (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. --
