For OOP we have override, which is great at preventing us from making mistakes by mispelling function names or using the wrong signature. Great.

For template, however, it's not as easy. Yes, we have template constraints, static if and static assert, but I found them wanting in certain situations. Consider this code, implementing a static visitor pattern:

    import std.stdio;
    import std.conv;

    enum Enum { Foo, Bar }

    enum isVisitor(T) = is(typeof(() {
        auto s = Struct();
        auto v = T();
        v.visit(s);
    }));

    enum hasAccept(T) = is(typeof(() {
        auto s = T();
        auto foo = FooVisitor();
        s.accept(foo);
        auto bar = BarVisitor();
        s.accept(bar);
    }));

    struct Struct {
        int i;
        void accept(V)(auto ref V visitor) if(isVisitor!V) {
            static if(visitor.type == Enum.Foo) {
                writeln("accept foo");
                visitor.visit(this);
            } else static if(visitor.type == Enum.Baz) {
                writeln("accept bar");
                visitor.visit(this);
            } else {
static assert(false, text("Unknown visitor type ", V.stringof));
            }
        }
        static assert(hasAccept!Struct);
    }

    struct FooVisitor {
        enum type = Enum.Foo;
        void visit(T)(auto ref T t) {
            writeln("foo visiting ", t);
        }
        static assert(isVisitor!FooVisitor);
    }

    struct BarVisitor {
        enum type = Enum.Bar;
        void visit(T)(auto ref T t) {
            writeln("bar visiting ", t);
        }
        static assert(isVisitor!BarVisitor);
    }

    void main() {
        auto v = FooVisitor();
        auto s = Struct(3);
        s.accept(v);
    }


The static asserts are there to verify that the structs I define actually do implement the interface I want them to. Which is great when they work, but tricky to find out why they don't when the assert fails. The code above has a bug, and compiling it I get this:

static_override.d(33): Error: static assert (hasAccept!(Struct)) is false
    Failed: ["dmd", "-v", "-o-", "static_override.d", "-I."]

It's good that it failed, but why? The problem here is that the lambda in hasAccept failed to compile, but the error messages the compiler would give me are hidden. The best I've come up with so far is to define this:

    mixin template assertHasAccept(T) {
        static if(!hasAccept!T) {
            void func() {
                auto s = T();
                auto foo = FooVisitor();
                s.accept(foo);
                auto bar = BarVisitor();
                s.accept(bar);
            }
        }
    }

And then I replaced the "static assert(hasAccept!Struct)" with "mixin assertHasAccept!Struct", which yields this:

    static_override.d(26): Error: no property 'Baz' for type 'int'
static_override.d(30): Error: static assert "Unknown visitor type BarVisitor" static_override.d(66): instantiated from here: accept!(BarVisitor)
    Failed: ["dmd", "-v", "-o-", "static_override.d", "-I."]

And the typo Bar->Baz is now revealed. I don't know if anyone else has given thought to this or has a better way of doing the above. Essentially I want a "static override" to check conformance to template interfaces at compile-time to avoid shooting myself in the foot, with helpful compiler error messages when I do. I've had a lot of errors from failing template constraints (good), but finding out _why_ was sometimes not easy. The compilation errors only happen at instantitation and the "is(typeof..." checks hide the errors.

Atila

Reply via email to