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.