www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Class templates with types determined at runtime

reply Doctor J <nobody nowhere.com> writes:
Let's say I have a class template whose type parameter will be drawn from a
small, fixed-ahead-of-time set of types, but which type won't be known until
runtime.  For example, say I would like to operate on a vector of
floating-point values read in from disk, but I won't know if it will be a
vector of floats or doubles until I read it in.  Is there a way to write one
codepath that handles both types, or do I need to switch on the (dynamic) type
and repeat the same code only with differing template instantiations?

Here is some code that illustrates the basic idea, with both the
switch-at-runtime implementation and my "dream in code" implementation that
doesn't compile:

----------------------------------
import std.stdio;
import std.math;
import std.c.stdlib;

class Container (T)
{
    this(T c)
    {
        this.c = c;
    }
    
    public:
    T c;
}

void dostuff (T) (T cont)
{
    writefln("%.12f", cont.c);
}

int main(string[] args)
{
    string precision = "double";
    if (args.length > 1 && args[1] == "single")
        precision = "single";

    // This works, but seems cumbersome
    switch (precision)
    {
        case "single":
            auto cont = new Container!(float)(PI);
            dostuff(cont);
            break;
        case "double":
            auto cont = new Container!(double)(PI);
            dostuff(cont);
            break;
        default:
            writefln("Error: unknown type '%s'.", precision);
            exit(1);
    }

    // Something like this would be nice
    auto cont = newContainer(precision);    
    dostuff(cont);
    
    return 0;
}


// Trying to return a "generic" Container, that can take on several types
// This gives error "class classtest.Container(T) is used as a type"
Container newContainer (string precision)
{
    switch (precision)
    {
        case "single":
            return new Container!(float)(PI);
        case "double":
            return new Container!(double)(PI);
        default:
            writefln("Error: unknown type '%s'.", precision);
            exit(1);
    }
}
----------------------------------------

It would be nice if the compiler could recognize "this is a function that can
return different types; I will generate code for each possibility and pick the
correct one at runtime."  But D doesn't support overloading functions on return
type.  So: is there a way to do this nicely, or do I bite the bullet and switch?
Apr 07 2009
next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
The major problem with this is that you're trying to get into a
situation where you don't know the type of T at compile-time, and you
CANNOT do that.

For example, your newContainer function can't return a "Container"
because "Container" isn't a concrete type; it's just a template.

The only thing I can think of that might help you is std.boxer (or
tango.core.Variant).  Variants and boxes can be passed around however
you'd like so long as you don't try to look inside.

As soon as you look inside, you need to have code specialised for each
supported type.

Something like this:

-----
import std.stdio;
import std.math;
import std.boxer;

void dostuff(Box cont)
{
    if( unboxable!(float)(cont) )
        writefln("%.12f", unbox!(float)(cont));

    else if( unboxable!(double)(cont) )
        writefln("%.12f", unbox!(double)(cont));
}

int main(string[] args)
{
    string precision = "double";
    if (args.length > 1 && args[1] == "single")
        precision = "single";

    auto cont = newContainer(precision);
    dostuff(cont);

    return 0;
}

Box newContainer(string precision)
{
    switch (precision)
    {
        case "single":
            return box( cast(float) PI );
        case "double":
            return box( cast(double) PI );
        default:
            /+
            // exit?  Eurgh!
            writefln("Error: unknown type '%s'.", precision);
            exit(1);
            +/
            throw new Exception("unknown type " ~ precision);
    }
}
-----

Hope that helps.

  -- Daniel
Apr 07 2009
parent Doctor J <nobody nowhere.com> writes:
Daniel Keep Wrote:
 
 The major problem with this is that you're trying to get into a
 situation where you don't know the type of T at compile-time, and you
 CANNOT do that.
Exactly -- I have been using Python lately. :) But wouldn't it be nice to have something like implicit template instantiation (or type inference) based on the possible return types of a (variant) function? I'm sure there are lots of good reasons why it isn't done, but it would be convenient... Thinking about this a little more, it seems the question is not "can I avoid the switch?" because it has to happen somewhere, but "can I put the switch in the module that determines the type of the class, rather than at the 'top level' where the class variable is declared?" Or put another way, can I declare a variable with unspecified type parameters, assign it the result of a variant-return-type function, and have the compiler generate code for all the possibilities behind the scenes? This is 98% of the way to dynamic typing; the only difference being the set of possible types is circumscribed at compile time. It seems to strike at the heart of the static/dynamic typing dichotomy. Can a function return multiple types? (And not just the polymorphism kind of multiple types.) It would get you many of the benefits of dynamic typing, but also some of the drawbacks ("what is the type of the expression I'm staring at? I don't know until I run it! Will it break with type T? I don't know until I run it!"). It would let generic types out of the confines of templates, which might frighten people. And at any rate, it sounds like a lot to ask of a strongly statically typed language.
 Something like this:
 
 -----
 import std.stdio;
 import std.math;
 import std.boxer;
 
 void dostuff(Box cont)
 {
     if( unboxable!(float)(cont) )
         writefln("%.12f", unbox!(float)(cont));
 
     else if( unboxable!(double)(cont) )
         writefln("%.12f", unbox!(double)(cont));
 }
 
 int main(string[] args)
 {
     string precision = "double";
     if (args.length > 1 && args[1] == "single")
         precision = "single";
 
     auto cont = newContainer(precision);
     dostuff(cont);
 
     return 0;
 }
 
 Box newContainer(string precision)
 {
     switch (precision)
     {
         case "single":
             return box( cast(float) PI );
         case "double":
             return box( cast(double) PI );
         default:
             /+
             // exit?  Eurgh!
             writefln("Error: unknown type '%s'.", precision);
             exit(1);
             +/
             throw new Exception("unknown type " ~ precision);
     }
 }
I appreciate the suggestion. But from an "ideal language" perspective, this is a workaround for a shortcoming of D, right? The price for a pretty main() is repeating the code I'm trying to avoid, twice. Mmm, tradeoffs. I'll think about it. Thanks for your help.
Apr 07 2009
prev sibling parent grauzone <none example.net> writes:
Not sure what you're actually trying to do, but you can do this:

class Container {
	abstract void dostuff();
}

class ContainerSDgsdg(T) : Container {
	override void dostuff() {
		//can use T here
	}
}

Container bla = new ContainerSDgsdg!(int)();
//can pass around "bla" freely, because class Container is not
//templated! the actual class is, but this doesn't matter.
bla.stuff();
Apr 07 2009