August 21, 2016
On Sunday, 21 August 2016 at 19:42:08 UTC, Lodovico Giaretta wrote:
> On Sunday, 21 August 2016 at 19:29:26 UTC, Engine Machine wrote:
>> [...]
>
> The problem of this code has nothing to do with aliases. They work correctly. The problem is variable shadowing. In the following code, Child has two x variables, one of which is only accessible from a Parent reference, the other only from a Child reference.
>
> class Parent
> {
>     int x;
> }
> class Child: Parent
> {
>     int x; // this shadows Parent.x
>     int y;
> }
>
> void main()
> {
>     Child child = new Child();
>     Parent parent = child;
>
>     child.x = child.y = 3;
>     parent.x = 2;
>
>     assert(child.x == 3);
>     assert((cast(Child)parent).x == 3);
>     assert((cast(Parent)child).x == 2);
>
>     assert(parent is child); // same object (remember that a class is already a pointer);
>     assert(&parent != &child); // but there are two different pointers on the stack (pointing to the same object)
> }

You're right. I didn't realize that variables could be shadowed in classes. Seems dangerous. D doesn't allow shadowing in a normal context and gives an error so I don't know why it wouldn't do that in classes. (since it wasn't giving an error I thought it wasn't shadowing)




August 21, 2016
On Sunday, 21 August 2016 at 20:36:54 UTC, Engine Machine wrote:
> On Sunday, 21 August 2016 at 19:42:08 UTC, Lodovico Giaretta wrote:
>> [...]
>
> You're right. I didn't realize that variables could be shadowed in classes. Seems dangerous. D doesn't allow shadowing in a normal context and gives an error so I don't know why it wouldn't do that in classes. (since it wasn't giving an error I thought it wasn't shadowing)

You are right. It is very bad. But as far as I know Java and C++ allow this too.
I will open an enhancement request (if there's none about this), to gather some feedback on the matter.
August 21, 2016
On 08/21/2016 09:29 PM, Engine Machine wrote:
> I know you like to play the right or wrong game, but did you ever learn
> that a single example does not prove the truth of something?

But you can show in a single example that something doesn't work. You tried to do that, and you did it with a simple example, which is always appreciated. But apparently, you were wrong. Happens to the best. And when the compiler prints "tmpl!a" that's indeed a little misleading.

You being wrong there doesn't mean that the Rebind template as I posted it works correctly, of course. But Jack didn't claim that. He just said that your example for how it doesn't work is wrong. And as far as I see, Rebind does work as expected. But I wouldn't be surprised if there is some edge case where things fall apart.

Also, if you're looking for help or civil discussion, I suggest you try a less antagonistic approach. Things like "I know you like to [play games]" and "did you ever learn [whatever]" do not set the tone for that.

> How about something more complex?
>
> import std.stdio;
> import std.meta, std.traits;
>
> class base { }

Style nitpick: Class names in PascalCase, please.

> template Rebind(alias instance, newArgs...)
> {
>     import std.traits: TemplateOf;
>     alias tmpl = TemplateOf!instance;
>     static if (newArgs.length > 0)
>         alias Rebind = tmpl!newArgs;
>     else
>         alias Rebind = base;
> }
>
> template EraseLast(A...)
> {
>     static if (A.length > 0)
>         alias EraseLast = Erase!(A[$-1], A);

This line can be simplified: alias EraseLast = A[0 .. $ - 1];

>     else
>         alias EraseLast = base;

This is quite surprising for a template that's called "EraseLast". I renamed it to "EraseLastOrBase" for myself when looking at this.

> }
>
>
>
> class T(A...) : Rebind!(T!A, EraseLast!A)

The core idea of Rebind isn't needed here, is it? You've got T, so you can just pass it directly. No need to extract it from T!A.

So, instead of using some form of Rebind, you can use a simpler template:

----
/* Instantiates tmpl with args, or if no args given, returns base. */
template InstantiateOrBase(alias tmpl, args ...)
{
    static if (args.length == 0) alias InstantiateOrBase = base;
    else alias InstantiateOrBase = tmpl!args;
}

class T(A...) : InstantiateOrBase!(T, EraseLast!A)
----

> {
>     int x;

Note that this means that every instantiation of T brings its own x. That is, T!foo declares an x, and T!(foo, bar) which inherits from T!foo adds another x. The same happens with y, z, and s, of course.

>     static if (A.length > 0 && A[0] == "Animal")
>     {
>         int y;
>         static if (A.length > 1 && A[1] == "Dog")
>         {
>             int z;
>             static if (A.length > 2 && A[2] == "Pug")
>                 int s;
>         }
>
>     }
> }
>
>
> pragma(msg, is(T!("Animal", "Dog", "Pug") : T!("Animal", "Dog")));
> pragma(msg, is(T!("Animal", "Dog", "Pug") : T!("Animal")));
> pragma(msg, is(T!("Animal", "Dog", "Pug") : base));
> pragma(msg, is(T!("Animal", "Dog") : T!("Animal")));
> pragma(msg, is(T!("Animal", "Dog") : base));
> pragma(msg, is(T!("Animal") : base));

Asserts are great to show that something holds. When someone like me makes changes to your code in order to understand it, the complier throws a nice loud error in their face when they mess up. So I changed these to static asserts for myself.

> // all true
>
>
>
> void main()
> {
>         auto t = new T!("Animal", "Dog", "Pug")();
>     T!("Animal", "Dog") q = t;
>         //T!("Animal", "Dog", "Pug") q = t; works but then q is not the
> super of t
>     t.x = 3;
>     t.y = 4;
>     t.z = 6;
>     t.s = 123;
>
>     q.y = 1;
>     writeln(t, ", ", typeof(t).stringof);
>     writeln(q, ", ", typeof(q).stringof);
>     writeln("---");
>     writeln(t.x);
>     writeln(t.y);
>     writeln(t.z);
>     writeln(t.s);
>     writeln("---");
>     writeln(q.x);
>     writeln(q.y);
>     writeln(q.z);
>     writeln("---");
>     writeln(t.y);
>     writeln("---");
>     writeln(&t);
>     writeln(&q);
>
> }
>
> Since the pragma's are true, it is obvious that the inheritance should
> work chain works. Yet q is not a reference to t as it should be, why is
> this?

It would help a lot if you would point out what parts of the output are surprising to you. If you let the reader figure it out themselves, chances are 1) people are not going to bother, and 2) if they do bother, they might miss your point.

Labeling the output also helps. By that I mean, if you write `writeln("t.x: ", t.x);`, the output can be read much more easily. I also think that you could have made your point with less output, which would again make it easier to follow. That is, just looking t.x/q.x would have been enough, no?

I guess you expect q.x and friends to be the same as t.x and friends. And since you put it there, you may also expect &t to be the same as &q.

About the members being different: That's because every instantiation brings its own x/y/z, as mentioned above. Lodovico has shown what happens here in his Child/Parent example.

About &t and &t: Those are the addresses of the class references themselves. To print the addresses of the objects, cast to void*:

----
writeln(cast(void*) t);
writeln(cast(void*) q); /* prints the same as the line above */
----

So, you're wrong again ;)
August 21, 2016
On Sunday, 21 August 2016 at 21:11:37 UTC, ag0aep6g wrote:
> On 08/21/2016 09:29 PM, Engine Machine wrote:
>> I know you like to play the right or wrong game, but did you ever learn
>> that a single example does not prove the truth of something?
>
> But you can show in a single example that something doesn't work. You tried to do that, and you did it with a simple example, which is always appreciated. But apparently, you were wrong. Happens to the best. And when the compiler prints "tmpl!a" that's indeed a little misleading.
>
> You being wrong there doesn't mean that the Rebind template as I posted it works correctly, of course. But Jack didn't claim that. He just said that your example for how it doesn't work is wrong. And as far as I see, Rebind does work as expected. But I wouldn't be surprised if there is some edge case where things fall apart.
>
> Also, if you're looking for help or civil discussion, I suggest you try a less antagonistic approach. Things like "I know you like to [play games]" and "did you ever learn [whatever]" do not set the tone for that.
>
>> How about something more complex?
>>
>> import std.stdio;
>> import std.meta, std.traits;
>>
>> class base { }
>
> Style nitpick: Class names in PascalCase, please.
>
>> template Rebind(alias instance, newArgs...)
>> {
>>     import std.traits: TemplateOf;
>>     alias tmpl = TemplateOf!instance;
>>     static if (newArgs.length > 0)
>>         alias Rebind = tmpl!newArgs;
>>     else
>>         alias Rebind = base;
>> }
>>
>> template EraseLast(A...)
>> {
>>     static if (A.length > 0)
>>         alias EraseLast = Erase!(A[$-1], A);
>
> This line can be simplified: alias EraseLast = A[0 .. $ - 1];
>
>>     else
>>         alias EraseLast = base;
>
> This is quite surprising for a template that's called "EraseLast". I renamed it to "EraseLastOrBase" for myself when looking at this.
>
>> }
>>
>>
>>
>> class T(A...) : Rebind!(T!A, EraseLast!A)
>
> The core idea of Rebind isn't needed here, is it? You've got T, so you can just pass it directly. No need to extract it from T!A.
>
> So, instead of using some form of Rebind, you can use a simpler template:
>
> ----
> /* Instantiates tmpl with args, or if no args given, returns base. */
> template InstantiateOrBase(alias tmpl, args ...)
> {
>     static if (args.length == 0) alias InstantiateOrBase = base;
>     else alias InstantiateOrBase = tmpl!args;
> }
>
> class T(A...) : InstantiateOrBase!(T, EraseLast!A)
> ----
>
>> {
>>     int x;
>
> Note that this means that every instantiation of T brings its own x. That is, T!foo declares an x, and T!(foo, bar) which inherits from T!foo adds another x. The same happens with y, z, and s, of course.
>
>>     static if (A.length > 0 && A[0] == "Animal")
>>     {
>>         int y;
>>         static if (A.length > 1 && A[1] == "Dog")
>>         {
>>             int z;
>>             static if (A.length > 2 && A[2] == "Pug")
>>                 int s;
>>         }
>>
>>     }
>> }
>>
>>
>> pragma(msg, is(T!("Animal", "Dog", "Pug") : T!("Animal", "Dog")));
>> pragma(msg, is(T!("Animal", "Dog", "Pug") : T!("Animal")));
>> pragma(msg, is(T!("Animal", "Dog", "Pug") : base));
>> pragma(msg, is(T!("Animal", "Dog") : T!("Animal")));
>> pragma(msg, is(T!("Animal", "Dog") : base));
>> pragma(msg, is(T!("Animal") : base));
>
> Asserts are great to show that something holds. When someone like me makes changes to your code in order to understand it, the complier throws a nice loud error in their face when they mess up. So I changed these to static asserts for myself.
>
>> // all true
>>
>>
>>
>> void main()
>> {
>>         auto t = new T!("Animal", "Dog", "Pug")();
>>     T!("Animal", "Dog") q = t;
>>         //T!("Animal", "Dog", "Pug") q = t; works but then q is not the
>> super of t
>>     t.x = 3;
>>     t.y = 4;
>>     t.z = 6;
>>     t.s = 123;
>>
>>     q.y = 1;
>>     writeln(t, ", ", typeof(t).stringof);
>>     writeln(q, ", ", typeof(q).stringof);
>>     writeln("---");
>>     writeln(t.x);
>>     writeln(t.y);
>>     writeln(t.z);
>>     writeln(t.s);
>>     writeln("---");
>>     writeln(q.x);
>>     writeln(q.y);
>>     writeln(q.z);
>>     writeln("---");
>>     writeln(t.y);
>>     writeln("---");
>>     writeln(&t);
>>     writeln(&q);
>>
>> }
>>
>> Since the pragma's are true, it is obvious that the inheritance should
>> work chain works. Yet q is not a reference to t as it should be, why is
>> this?
>
> It would help a lot if you would point out what parts of the output are surprising to you. If you let the reader figure it out themselves, chances are 1) people are not going to bother, and 2) if they do bother, they might miss your point.
>
> Labeling the output also helps. By that I mean, if you write `writeln("t.x: ", t.x);`, the output can be read much more easily. I also think that you could have made your point with less output, which would again make it easier to follow. That is, just looking t.x/q.x would have been enough, no?
>
> I guess you expect q.x and friends to be the same as t.x and friends. And since you put it there, you may also expect &t to be the same as &q.
>
> About the members being different: That's because every instantiation brings its own x/y/z, as mentioned above. Lodovico has shown what happens here in his Child/Parent example.
>
> About &t and &t: Those are the addresses of the class references themselves. To print the addresses of the objects, cast to void*:
>
> ----
> writeln(cast(void*) t);
> writeln(cast(void*) q); /* prints the same as the line above */
> ----
>
> So, you're wrong again ;)

So, the point is, I don't like putting a lot of work in to something unless I know it works. D is horrible in showing what is actually going on most of the time and I have to figure out and try many different things. If it has a bug or error msg that isn't informative then I have to try something again, and keep trying until the problem is solved. I've never had these problems with modern compilers. I assume it is due to D's meta complexity. It is amazing on one hand but sucks on the other.

Anyways, This is what I finally have:

import std.meta, std.traits;  	

template Rebind(alias instance, newArgs...)
{
    import std.traits: TemplateOf;
	alias Seq(T...)=T;
    alias tmpl = TemplateOf!instance;
	static if (newArgs.length > 0)
	    alias Rebind = tmpl!newArgs;
	else
		alias Rebind = Seq!();
}



template EraseLast(A...)
{
	alias Seq(T...)=T;
	static if (A.length > 0)
		alias EraseLast = Erase!(A[$-1], A);
	else
		alias EraseLast = Seq!();
}



class T(A...) : Rebind!(T!A, EraseLast!A)
{	
	pragma(msg, A);
	static if (A.length == 0)
	{
		pragma(msg, "BASE!!");
	} else
	static if (A[$-1] == "Animal")
	{
		int x;
		int y;
	} else
	static if (A[$-1] == "Dog")
	{
		int z;
	} else
	static if (A[$-1] == "Pug")
	{
		int s;
	} else pragma(msg, A[$-1], " not a defined class of ", this.stringof);
}



This is the problem. Rebind(or even if your modification of InstantiateOrBase, which requires a base), does not provide the base class.

I want the above code to simply provide itself as a base when it is at the "top" of the hierarchy.

The idea is suppose to be simple:

One has a hierarchy a super b super c. (e.g. Animal <= Dog <= Pug)

There is a base to A, which contains all the base stuff, It is taken to 0 template parameters: T!() <= T!"Animal" <= T!("Animal", "Dog") <= T!("Animal", "Dog", "Pug")

T!()'s "data" is specified in the class just like all the other derivations. I don't want to have to specify an external base class as in your InstaniateOrBase. Why? Because!!! (There should be no need to, and if one goes this route of creating classes, it should be all or nothing, else there is no real benefit)


Now, I can do somethign like T!("Base", "Animal", etc) but then this requires specifying "Base" in every construction, which is unnecessary since it always exists.

So, I basically want to conditionally inherit. If A... has length 0(in T), i.e., T!() then we get the base class. I've tried using Object as a substitute and all that mess but nothing works properly. Usually results in a circular definition.

It seems that when one uses `class T(A...) : X`, one can't, for some X, not have inheritance. The compiler expects X to be something inheritable from no matter what. I could be wrong, but this seems to be the case. i.e., there is no such thing as conditional inheritance in D, e.g.,

class T(A...) if (A.length > 0) : X // (inherits from X only if A.length > 0)
{

}

Or, say, X is "null" or an empty sequence.

Hopefully I am truly wrong and D can do this.

Again, all I want to do is specify the "base class" inside the class rather than create a new class outside. I can do this for all the derivations now but not for base.









August 22, 2016
On 08/22/2016 12:06 AM, Engine Machine wrote:
> T!()'s "data" is specified in the class just like all the other
> derivations. I don't want to have to specify an external base class as
> in your InstaniateOrBase. Why? Because!!! (There should be no need to,
> and if one goes this route of creating classes, it should be all or
> nothing, else there is no real benefit)

You make it sound like I came up with the external base class, but I just took that from your code.

[...]
> It seems that when one uses `class T(A...) : X`, one can't, for some X,
> not have inheritance. The compiler expects X to be something inheritable
> from no matter what.

I didn't know it, but your code shows that X can also be an empty compile time list (Seq). I have no idea if this is in the spec, or if it just happens to work with dmd.

If it's valid D, you just need a template that goes from T and A to T!(A[0 .. $ - 1]), or if A is already empty, to the empty Seq.

Like so:

----
template InstantiateOrEmptySeq(alias tmpl, args...)
{
    alias Seq(T...)=T;
    static if (args.length > 0)
        alias InstantiateOrEmptySeq = tmpl!(args[0 .. $ - 1]);
    else
        alias InstantiateOrEmptySeq = Seq!();
}

class T(A...) : InstantiateOrEmptySeq!(T, A)
{
    ...
}
----


August 22, 2016
On Monday, 22 August 2016 at 00:22:48 UTC, ag0aep6g wrote:
> On 08/22/2016 12:06 AM, Engine Machine wrote:
>> T!()'s "data" is specified in the class just like all the other
>> derivations. I don't want to have to specify an external base class as
>> in your InstaniateOrBase. Why? Because!!! (There should be no need to,
>> and if one goes this route of creating classes, it should be all or
>> nothing, else there is no real benefit)
>
> You make it sound like I came up with the external base class, but I just took that from your code.
>
No, what I meant was I don't want a base. I only added it in my code so it would compile/do what I want.

> [...]
>> It seems that when one uses `class T(A...) : X`, one can't, for some X,
>> not have inheritance. The compiler expects X to be something inheritable
>> from no matter what.
>
> I didn't know it, but your code shows that X can also be an empty compile time list (Seq). I have no idea if this is in the spec, or if it just happens to work with dmd.
>
> If it's valid D, you just need a template that goes from T and A to T!(A[0 .. $ - 1]), or if A is already empty, to the empty Seq.
>
> Like so:
>
> ----
> template InstantiateOrEmptySeq(alias tmpl, args...)
> {
>     alias Seq(T...)=T;
>     static if (args.length > 0)
>         alias InstantiateOrEmptySeq = tmpl!(args[0 .. $ - 1]);
>     else
>         alias InstantiateOrEmptySeq = Seq!();
> }
>
> class T(A...) : InstantiateOrEmptySeq!(T, A)
> {
>     ...
> }
> ----

Yes! I though I tried that from Timon's original solution.

The following code works and does what I want!

template InstantiateOrEmptySeq(alias tmpl, args...)
{
    alias Seq(T...)=T;
    static if (args.length > 0)
        alias InstantiateOrEmptySeq = tmpl!(args[0 .. $-1]);
    else
        alias InstantiateOrEmptySeq = Seq!();
}


class T(A...) : InstantiateOrEmptySeq!(T, A)
{	
	static if (A.length == 0)
	{
                // Base class
		int x;
	} else	
	static if (A[$-1] == "Animal")
	{
		int y;
	} else
	static if (A[$-1] == "Dog")
	{
		int z;
	} else
	static if (A[$-1] == "Pug")
	{
		int s;
	} else static assert(A[$-1]~" not a defined class of "~this.stringof);
}

The only down side is that the static if's are flat. They don't show the relationship between, say, Dog an Animal, that a nested set of static if's would show, but I think this is not possible due to the recursion and shadowing(I imagine one could test if the variables exist but that is messy).

Anyways, Thanks for the help.

I think this is just Timons answer anyways. What the original confusion was, was the shadowing, which I thought would not happen(or at least expected an error), Lodovico solved that.




August 22, 2016
On Monday, 22 August 2016 at 00:43:00 UTC, Engine Machine wrote:
> The following code works and does what I want!
>
> template InstantiateOrEmptySeq(alias tmpl, args...)
> {
>     alias Seq(T...)=T;
>     static if (args.length > 0)
>         alias InstantiateOrEmptySeq = tmpl!(args[0 .. $-1]);
>     else
>         alias InstantiateOrEmptySeq = Seq!();
> }
>
>
> class T(A...) : InstantiateOrEmptySeq!(T, A)
> {	
> 	static if (A.length == 0)
> 	{
>                 // Base class
> 		int x;
> 	} else	
> 	static if (A[$-1] == "Animal")
> 	{
> 		int y;
> 	} else
> 	static if (A[$-1] == "Dog")
> 	{
> 		int z;
> 	} else
> 	static if (A[$-1] == "Pug")
> 	{
> 		int s;
> 	} else static assert(A[$-1]~" not a defined class of "~this.stringof);
> }

Why don't you like a cleaner (in my opinion) solution?

class T() {
	// Base class
	int x;
}

class T(A...) : T!(A[0..$-1]) {	
	static if (A[$-1] == "Animal")
	{
		int y;
	} else
	static if (A[$-1] == "Dog")
	{
		int z;
	} else
	static if (A[$-1] == "Pug")
	{
		int s;
	} else static assert(A[$-1]~" not a defined class of "~this.stringof);
}
August 22, 2016
On Monday, 22 August 2016 at 07:54:36 UTC, Jack Applegame wrote:
> On Monday, 22 August 2016 at 00:43:00 UTC, Engine Machine wrote:
>> The following code works and does what I want!
>>
>> template InstantiateOrEmptySeq(alias tmpl, args...)
>> {
>>     alias Seq(T...)=T;
>>     static if (args.length > 0)
>>         alias InstantiateOrEmptySeq = tmpl!(args[0 .. $-1]);
>>     else
>>         alias InstantiateOrEmptySeq = Seq!();
>> }
>>
>>
>> class T(A...) : InstantiateOrEmptySeq!(T, A)
>> {	
>> 	static if (A.length == 0)
>> 	{
>>                 // Base class
>> 		int x;
>> 	} else	
>> 	static if (A[$-1] == "Animal")
>> 	{
>> 		int y;
>> 	} else
>> 	static if (A[$-1] == "Dog")
>> 	{
>> 		int z;
>> 	} else
>> 	static if (A[$-1] == "Pug")
>> 	{
>> 		int s;
>> 	} else static assert(A[$-1]~" not a defined class of "~this.stringof);
>> }
>
> Why don't you like a cleaner (in my opinion) solution?
>
> class T() {
> 	// Base class
> 	int x;
> }
>
> class T(A...) : T!(A[0..$-1]) {	
> 	static if (A[$-1] == "Animal")
> 	{
> 		int y;
> 	} else
> 	static if (A[$-1] == "Dog")
> 	{
> 		int z;
> 	} else
> 	static if (A[$-1] == "Pug")
> 	{
> 		int s;
> 	} else static assert(A[$-1]~" not a defined class of "~this.stringof);
> }

How do you seriously think this is cleaner/simpler? You have two classes. Their is no uniformity between them. You have uniformity between all the derived classes then have a special case for the base class. A certain easy to follow pattern is set up but needlessly break it for one case. Why?










August 22, 2016
On 08/22/2016 08:04 PM, Engine Machine wrote:
> How do you seriously think this is cleaner/simpler? You have two
> classes. Their is no uniformity between them. You have uniformity
> between all the derived classes then have a special case for the base
> class. A certain easy to follow pattern is set up but needlessly break
> it for one case. Why?

It avoids the relatively obscure (and badly named, by me) InstantiateOrEmptySeq template.

You can take this further with template constraints. Gives it a more uniform appearance at the price of some repetition:

----
class T()
{
    int x;
}

class T(A...) : T!(A[0..$-1])
    if (A.length > 0 && A[$-1] == "Animal")
{
    int y;
}

class T(A...) : T!(A[0..$-1])
    if (A.length > 0 && A[$-1] == "Dog")
{
    int z;
}

class T(A...) : T!(A[0..$-1])
    if (A.length > 0 && A[$-1] == "Pug")
{
    int s;
}
----

I wouldn't say that this is obviously, objectively, significantly better than the version with the helper template, though.

By the way, it's not obvious to me what this whole thing achieves in practice. And whatever benefit there is, it has to be weighed against the cost of the added complexity.

I don't mean to say that this is not worth pursuing. Just a friendly reminder that over-engineering is something to look out for, from someone who is prone to over-engineering himself.
August 22, 2016
On Monday, 22 August 2016 at 18:48:12 UTC, ag0aep6g wrote:
> On 08/22/2016 08:04 PM, Engine Machine wrote:
>> How do you seriously think this is cleaner/simpler? You have two
>> classes. Their is no uniformity between them. You have uniformity
>> between all the derived classes then have a special case for the base
>> class. A certain easy to follow pattern is set up but needlessly break
>> it for one case. Why?
>
> It avoids the relatively obscure (and badly named, by me) InstantiateOrEmptySeq template.
>
> You can take this further with template constraints. Gives it a more uniform appearance at the price of some repetition:
>
> ----
> class T()
> {
>     int x;
> }
>
> class T(A...) : T!(A[0..$-1])
>     if (A.length > 0 && A[$-1] == "Animal")
> {
>     int y;
> }
>
> class T(A...) : T!(A[0..$-1])
>     if (A.length > 0 && A[$-1] == "Dog")
> {
>     int z;
> }
>
> class T(A...) : T!(A[0..$-1])
>     if (A.length > 0 && A[$-1] == "Pug")
> {
>     int s;
> }
> ----
>
> I wouldn't say that this is obviously, objectively, significantly better than the version with the helper template, though.
>

Yeah, but a name means nothing. Change it if you want ;)


> By the way, it's not obvious to me what this whole thing achieves in practice. And whatever benefit there is, it has to be weighed against the cost of the added complexity.

What it achieves is a uniform way to create a hierarchical relationship without excessive verbosity. Now, the static ifs are probably the most verbose part, a static switch would help:

static switch (A)
{
   case "Animal":
   return;
   ...
}


But only slightly.

> I don't mean to say that this is not worth pursuing. Just a friendly reminder that over-engineering is something to look out for, from someone who is prone to over-engineering himself.


Ultimately it is just syntax, it does nothing over what D already does. It doesn't create anything new. In some cases it simplifies. If it does, then great, if it doesn't then don't use it. While I would prefer a totally different syntax, I also like to keep things organized. Having multiple classes floating around for certain things is just not needed so there should be an option.