Thread overview
Class templates with types determined at runtime
Apr 08, 2009
Doctor J
Apr 08, 2009
Daniel Keep
Apr 08, 2009
Doctor J
Apr 08, 2009
grauzone
April 08, 2009
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?
April 08, 2009
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

April 08, 2009
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.


April 08, 2009
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();