Problem: Cannot create class out of nothing using witchcraft

DoctorCaptain garble at harble.com
Tue Oct 15 01:03:44 PDT 2013


dmd v2.063.2

Hi there! I'm terribly hopeful that you're more interested in the
problem at hand than my choice of title.

I've been using D for a while as my language of choice for
various projects here and there, and I've recently discovered
that template programming is magic. This is good. As part of the
process of finding anything magic that also works, I've been
going completely overboard with it. This involves, among other
things, creating empty classes using templates and mixins at
compile time, to build an inheritance hierarchy to do fancy
things. This is all essentially irrelevant, but brings us to my
issue.

The next logical step in my adventure here was to see if I could
create classes that weren't empty, but in fact has data members,
generated at compile time using variadic templates. The first
run-through went super well, and I was able to create classes
with arbitrary primitive data types.

However, ultimately I wanted these compile-time-generated classes
with arbitrary data members to have data members that were of the
type of the other classes that were also generated at compile
time.

That is to say, I want to generate, at compile time, classes like
this:

class MyClass {
      crazyTemplate!("MagicClassOne").MagicClassOne t1;
      crazyTemplate2!("MagicClassTwo").MagicClassTwo t2;
}

such that crazyTemplate and crazyTemplate2 each generate a class
definition at compile time, accessible as defined by the string
parameter.

For example:

template crazyTemplate(string classname) {
      mixin(`class ` ~ className ~ ` {}`);
}

The bare class-generating templates work, and generating classes
that have arbitrary members at compile time using variadic
templates given an arbitrary list of basic types works, but as
soon as I try to use these template-generated classes as the
types of the members of this variadic template, things go awry.

I feel like what I'm trying to explain is a bit difficult to
parse without a minimal working example, so here is one:

code
-------------
import std.stdio;
import std.typetuple;
import std.traits;
import std.conv;

class BaseClass {}

template ChildT(string className)
{
      mixin(`class ` ~ className ~ ` : BaseClass {}`);
}

template isChildOrBaseClass(T)
{
      enum bool isChildOrBaseClass = is(T : BaseClass);
}

template GrabBagT(string className, T...)
      if (allSatisfy!(isChildOrBaseClass, T))
{
      mixin(genClassStr!(T)(className));
}

string genClassStr(T...)(string className)
{
      string classStr = "";
      classStr ~= `static class ` ~ className ~ ` : BaseClass`;
      classStr ~= `{`;
      // Demonstrate that the template itself is not the problem
      classStr ~= `    ChildT!("BestChild").BestChild t1000;`;
      // Add arbitrary data members
      foreach (i, TI; T)
      {
          // Neither of these work with the generated classes, but
the first
          // will work if GrabBagT is called with primitive types
and its
          // constraint is commented out
          //classStr ~= fullyQualifiedName!(TI) ~ ` t` ~
to!(string)(i) ~ `;`;
          //classStr ~= __traits(identifier, TI) ~ ` t` ~
to!(string)(i) ~ `;`;
      }
      classStr ~= `    void printAttempts() {`;
      foreach (i, TI; T)
      {
          classStr ~= `    writeln(` ~ to!string(i) ~ `);`;
      }
      classStr ~= `    }`;
      classStr ~= `}`;
      return classStr;
}

int main(string[] args)
{
      alias ChildT!("WorstChild").WorstChild WorstChild;
      alias ChildT!("MiddleChild").MiddleChild MiddleChild;

      auto magicObject = new GrabBagT!(
          "MagicClass", WorstChild, MiddleChild).MagicClass();

      //auto magicObject = new GrabBagT!(
      //    "MagicClass", int, float).MagicClass();

      magicObject.printAttempts();

      return 0;
}
-------------

That should compile and print out "0" and "1". Note the template
constraint that bars the template instantiator from trying to
instantiate GrabBagT with anything other than types that can be
implicitly treated as type BaseClass. I tried using
__traits(identifier) and fullyQualifiedName to get the actual
type of the alias passed in (like the BestChild declaration), but
the latter gives a mangled name, and the former gives just
whatever the bare alias was.

In any case. If the fullyQualifiedName line is uncommented, the
error is:

------------
classAttributeGen.d(22): Error: undefined identifier
'__T6ChildTVAyaa10_576f7273744368696c64Z'
classAttributeGen.d(22): Error:
classAttributeGen.__T6ChildTVAyaa10_576f7273744368696c64Z.WorstChild
is used as a type
classAttributeGen.d(22): Error: undefined identifier
'__T6ChildTVAyaa11_4d6964646c654368696c64Z'
classAttributeGen.d(22): Error:
classAttributeGen.__T6ChildTVAyaa11_4d6964646c654368696c64Z.MiddleChild
is used as a type
classAttributeGen.d(54): Error: template instance
classAttributeGen.GrabBagT!("MagicClass", WorstChild,
MiddleChild) error instantiating
------------

If the __traits line is uncommented instead, the error is:

------------
classAttributeGen.d(22): Error: undefined identifier WorstChild
classAttributeGen.d(22): Error: undefined identifier MiddleChild
classAttributeGen.d(54): Error: template instance
classAttributeGen.GrabBagT!("MagicClass", WorstChild,
MiddleChild) error instantiating
------------

Of particular note is that we are giving the class a
ChildT!("BestChild").BestChild member, which shows that simply
using the empty-class-generating-templates is not the problem,
but rather the way the type tuple is fed in to the variadic
template.

If the GrabBagT constraint is commented out, the uncommented
magicObject instantiation is commented, the commented magicObject
instantiation is uncommented, and the fullyQualifiedName line is
uncommented, then this will also compile, demonstrating that a
tuple of primitive data types can be used to generate classes at
compile time with arbitrary, primitive data members.

Ultimately, the problem I am trying to solve is generating class
definitions at compile time with arbitrary, templated data
members. If this is the wrong way to do it, by all means point me
in the right direction. However, as an exercise to the community,
is it even possible to do it the way I'm attempting here? Is it
possible to do at all? Is there something awful about how
templates are passed around that makes it fundamentally
impossible to get their underlying type information, like with
the working BestClass member, if they're passed in as template
type parameters?

If what I am asking is unclear, I will be more than happy to
explain in a different way. I tried to be simultaneously as
succinct and as comprehensive as possible with what the issue is.
I also tried to find the answer to this problem, as I have with
every other problem I've faced with D, through an exhaustive
search of the various resources available to us, but alas I could
find nothing. Of course, this is a fairly esoteric scenario, so
that's entirely acceptable. That said, ideally the definitive
answer can be found in future searches by finding this post.

With a quiet but definitive stepping-aside to the inquiry of
"What why are you doing this."


More information about the Digitalmars-d-learn mailing list