www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - class template conflict

reply Michelle Long <HappyDance321 gmail.com> writes:
class X
{

}

class X(int N) : X
{

}

Is there any real reason we can't do this?

It is very nice to be able to treat X like the base and X!n as a 
derived class.

Sure we can do

class X(int N) : X!0
{
    static if(N == 0)
    {
    }
}

but this is very ugly, in my code I always have to use X!0 as the 
basis!

I do not think there is any harm to allow this since the 
templated class always has to specify N. It is not like we can do

class X(int N = 0) : X
{
    static if(N == 0)
    {
    }
}


Actually we can, so... I don't see the point in not allowing the 
first case, they are logically equivalent. That static if is just 
ugly and it is defining the base class inside the derived class 
which seems unnatural.
Dec 23 2018
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 23 December 2018 at 12:09:31 UTC, Michelle Long wrote:
 class X
 {

 }

 class X(int N) : X
 {

 }

 Is there any real reason we can't do this?

 It is very nice to be able to treat X like the base and X!n as 
 a derived class.

 Sure we can do

 class X(int N) : X!0
 {
    static if(N == 0)
    {
    }
 }

 but this is very ugly, in my code I always have to use X!0 as 
 the basis!

 I do not think there is any harm to allow this since the 
 templated class always has to specify N. It is not like we can 
 do

 class X(int N = 0) : X
 {
    static if(N == 0)
    {
    }
 }


 Actually we can, so... I don't see the point in not allowing 
 the first case, they are logically equivalent. That static if 
 is just ugly and it is defining the base class inside the 
 derived class which seems unnatural.
You have this option too: ``` template X(N...) if (N.length == 0 || N.length == 1 && is(typeof(N[0]) == int)) { static if (N.length == 0) class X {} else class X : X!() {} } auto base = new X!(); auto derived = new X!8; ``` More simple is : do not use the same identifier ;)
Dec 23 2018
parent reply Michelle Long <HappyDance321 gmail.com> writes:
 More simple is : do not use the same identifier ;)
The whole point is to use the same identifier ;/
Dec 23 2018
parent reply bauss <jj_1337 live.dk> writes:
On Monday, 24 December 2018 at 00:24:05 UTC, Michelle Long wrote:
 More simple is : do not use the same identifier ;)
The whole point is to use the same identifier ;/
I think there is a bigger problem at stake here in terms of software architecture. What's the point needed for them to have the same identifier? Clearly the two classes will have two different functions and should be named accordingly. Ex. class X { ... } class X(int N) { ... } Could be something like: class X { ... } class XWithArguments { ... } There is absolutely no point of them having same identifiers unless they did the exact same thing and in that case you'd probably just not want this anyway. A solution to this would be something like: interface X { } class XWithoutArguments : X { } class XWithArguments : X { }
Dec 25 2018
next sibling parent Neia Neutuladh <neia ikeran.org> writes:
On Tue, 25 Dec 2018 18:34:04 +0000, bauss wrote:
 I think there is a bigger problem at stake here in terms of software
 architecture.
 
 What's the point needed for them to have the same identifier?
A probably abstract base class with only one child class. Normally you have "Foo" and "FooImpl", or "IFoo" and "Foo", but that's ugly. This was the primary example that Michelle Long gave. I'd expect that a class Foo {} class Foo<T> : Foo {} class Foo<T1, T2> : Foo {} happen to have type parameters. In D, you'd have one class and two templates, and you can't overload two symbols of different kinds, so you have to write it as: class Foo() {} class Foo(T): Foo!() {} class Foo(T, U): Foo!() {} In Java, for legacy reasons, this pattern is baked into the language; Foo<T> always has another class, Foo, that it implicitly casts to and from. Some people take advantage of that. Most people who do both metaprogramming and OOP in D and have been doing that for a nontrivial amount of time probably encountered this exact same thing in D. I encountered it the first time before D2 was a thing.
 Clearly the two classes will have two different functions and should be
 named accordingly.
They will have different implementations and may have different interfaces. The proposed use case is inheritance, so X!10's public interface will be a superset of X's (and likely identical). To give a slightly less contrived example, let's say you want to have some functions available to a scripting language. (I'm doing something like this in a side project for a CLI spreadsheet program.) Each function has a display name, help text, argument validation, and the function body. If I had done this in an OOP style (and I might end up reworking it to look more like this), I'd have something like: interface Function { string helpText(); string name(); Nullable!Error validateParameters(Val[] parameters); Val execute(Val[] parameters); } And then I'd have a convenience mechanism to produce a conforming class from a function with UDAs: class FunctionImpl(alias fn) : Function { override: string helpText() { return getUDAs!(fn, Help)[0].text; } string name() { return __traits(identifier, fn); } // etc } It would be slightly nicer to just have "Function" everywhere instead of both Function and FunctionImpl. Not enough to justify the complexity of the symbol lookup rules required. Not enough to make `class Foo(T)` mean something different from `template Foo(T) class Foo`. But it *would* be slightly nicer, and it would make things slightly more straightforward for
Dec 25 2018
prev sibling parent Michelle Long <HappyDance321 gmail.com> writes:
On Tuesday, 25 December 2018 at 18:34:04 UTC, bauss wrote:
 On Monday, 24 December 2018 at 00:24:05 UTC, Michelle Long 
 wrote:
 More simple is : do not use the same identifier ;)
The whole point is to use the same identifier ;/
I think there is a bigger problem at stake here in terms of software architecture.
No, the problem is reasoning from the conclusion...
Dec 25 2018
prev sibling next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 12/23/18 7:09 AM, Michelle Long wrote:
 class X
 {
 
 }
 
 class X(int N) : X
 {
 
 }
 
 Is there any real reason we can't do this?
I think it has less to do with class names and more to do with symbol overloading. The only place I think templates are allowed to overload names with non-templates is functions, which actually was not always the case (you used to have to only have templates or non templates as function overloads).
 
 It is very nice to be able to treat X like the base and X!n as a derived 
 class.
The problem I see is: template foo(alias A) { ... } foo!X Did you mean class X or template X? For functions, this is OK, because it's one overload set. -Steve
Dec 23 2018
prev sibling parent reply Daniel Kozak <kozzi11 gmail.com> writes:
ne 23. 12. 2018 13:10 odes=C3=ADlatel Michelle Long via Digitalmars-d-learn=
 <
digitalmars-d-learn puremagic.com> napsal:

 class X
 {

 }

 class X(int N) : X
 {

 }

 Is there any real reason we can't do this?
Actually yes. It would break almost all of my code. In D you can do thing like this: class X(int N) { X something; // it is same as X!N something; } So I do not need to write X!N everywhere inside X class template
Dec 24 2018
parent reply Michelle Long <HappyDance321 gmail.com> writes:
On Monday, 24 December 2018 at 22:55:55 UTC, Daniel Kozak wrote:
 ne 23. 12. 2018 13:10 odesílatel Michelle Long via 
 Digitalmars-d-learn < digitalmars-d-learn puremagic.com> napsal:

 class X
 {

 }

 class X(int N) : X
 {

 }

 Is there any real reason we can't do this?
Actually yes. It would break almost all of my code.
How would it break your code?
 In D you can do thing like this:
 class X(int N)
 {
 X something; // it is same as X!N something;
 }

 So I do not need to write X!N everywhere inside X class template
But I am not talking about inside the template being used. The whole point of doing this is so that one can refer to the base class using the same name as the derived with a template parameter to make a composite structure. X!N should be totally different than X. The fact that you can use X inside a class to refer to X!N is a hack... and in any case should not effect what I'm talking about because it is only used in the inherited part.. which it would be circular to use it as it is: class X(int N) : X { } creates circular inheritance. so for the inherited part it should never be used and you never use it in your code like that... so it won't break anything. Also, as long as there is no other symbol with that name it won't break anything. Suppose this did work: class X; class X(int N) : X // (Here X refers to the base class above { // Using X can still be X!N since we can just alias the original X away. } class X; alias XX = X; class X(int N) : X { X x; // X!N x; XX xx; // X = x; } So it would not break anything. It really has nothing to do with what one does inside a template but how it looks to the outside.
Dec 25 2018
parent reply Neia Neutuladh <neia ikeran.org> writes:
On Tue, 25 Dec 2018 13:03:13 +0000, Michelle Long wrote:
 But I am not talking about inside the template being used. The whole
 point of doing this is so that one can refer to the base class using the
 same name as the derived with a template parameter to make a composite
 structure.
The following are entirely equivalent: class X(int N) : X {} template X(int N) { class X : X {} } You want to be able to do, essentially: class X {} template X(int N) { // `: X` somehow refers to the X in the outer scope class X : X {} } And sure, this specific case obviously doesn't work if the `: X` refers to the template or the class inside the template. But the symbol lookup rules aren't "try everything and see what sticks". You look up the nearest symbol and then you just use that. It would be like arguing that, in the following example, the compiler should know that ints aren't callable and should call the function: void foo() { writeln("merr critmis"); } void main() { int foo = 10; foo(); } Which isn't obviously wrong, but it does make things harder to understand.
Dec 25 2018
parent Neia Neutuladh <neia ikeran.org> writes:
On Tue, 25 Dec 2018 16:55:36 +0000, Neia Neutuladh wrote:

And I forgot part of it.

Let's say we did the work to make this function:

    class X {}
    template X(int N)
    {
      // `: X` somehow refers to the X in the outer scope
      class X : X {}
    }

How do you distinguish between the base class and the derived class in 
there? You'd have to use typeof(this) and typeof(super) everywhere.

And externally, how do you refer to class X and template X separately? If 
you have a template with an alias parameter and pass X, how do you pass 
class-X and how do you pass template-X?

This is already unpleasant with functions, and there's a way to 
distinguish them.
Dec 25 2018