import tardy;
interface ITransformer {
int transform(int) @safe pure const;
}
alias Transformer = Polymorphic!ITransformer;
int xform(Transformer t) {
return t.transform(3);
}
struct Adder {
int i;
int transform(int j) @safe pure const { return i + j; }
}
struct Plus1 {
int transform(int i) @safe pure const { return i + 1; }
}
unittest {
assert(xform(Transformer(Adder(2))) == 5);
assert(xform(Transformer(Adder(3))) == 6);
assert(xform(Transformer(Plus1())) == 4);
}
Traditional inheritance-based runtime polymorphism has a few drawbacks:
Louis Dionne explained it better and in more detail in his talk.
Tardy makes it so:
Instances may be created by passing a value to Polymorphic
's constructor or by emplacing them
and having Polymorphic
call the instance type's contructor itself. The examples above show
how to construct using a value. To explicitly instantiate a particular type:
auto t = Polymorphic!MyInterface.create!MyType(arg0, arg1, arg2, ...);
One can also pass modules to create
where Polymorphic
should look for UFCS candidates:
// Using the `Transfomer` example above, and assuming there's a
// UFCS function in one of "mymod0" or "mymod1",
// this constructs an `int` "instance"
auto t = Transformer.create!("mymod0", "mymod1")(42);
The vtable type is constructed at compile-time by reflecting on the interface passed
as the first template parameter passed to Polymorphic
. To not overly constrain what users
may do with their types (@safe
, pure
, ...), the default is @safe
, but attributes
can be specified for each of these compiler-generated member functions:
interface MyInterface {
import std.traits: FA = FunctionAttribute;
enum CopyConstructorAttrs = FA.safe | FA.pure_;
enum DestructorAttrs = FA.pure_ | FA.nogc;
}