www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Static override?

reply "Atila Neves" <atila.neves gmail.com> writes:
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
Apr 04 2014
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Atila Neves:

 I don't know if anyone else has given thought to this
 or has a better way of doing the above.
Is this related? http://msdn.microsoft.com/en-us/library/435f1dw2.aspx http://msdn.microsoft.com/en-us/library/sd2w2ew5.aspx Bye, bearophile
Apr 04 2014
parent "Atila Neves" <atila.neves gmail.com> writes:
Sort of.

Atila

On Friday, 4 April 2014 at 10:37:04 UTC, bearophile wrote:
 Atila Neves:

 I don't know if anyone else has given thought to this
 or has a better way of doing the above.
Is this related? http://msdn.microsoft.com/en-us/library/435f1dw2.aspx http://msdn.microsoft.com/en-us/library/sd2w2ew5.aspx Bye, bearophile
Apr 04 2014
prev sibling next sibling parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 04/04/14 11:22, Atila Neves wrote:
     enum hasAccept(T) = is(typeof(() {
         auto s = T();
         auto foo = FooVisitor();
         s.accept(foo);
         auto bar = BarVisitor();
         s.accept(bar);
     }));
 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.
template hasAccept(T) { static iface(CT)(CT t) { auto foo = FooVisitor(); t.accept(foo); auto bar = BarVisitor(); t.accept(bar); } static if (is(typeof(iface!T))) enum hasAccept = true; else enum hasAccept = &iface!T; } [Should probably be split into three parts: 1) the if-definition, 2) the does-T-implement-the-if check, and 3) the T-must-implement-the-if assertion.] artur
Apr 04 2014
parent reply "Atila Neves" <atila.neves gmail.com> writes:
That gives me this:
     static_override.d(22): Error: variable 
static_override.Struct.hasAccept!(Struct).hasAccept had semantic 
errors when compiling

Which is better than before but still hiding the actuall error. 
But this works as expected:

     template hasAccept(T) {
         static iface(CT)(CT t) {
             auto foo = FooVisitor();
             t.accept(foo);
             auto bar = BarVisitor();
             t.accept(bar);
         }
         static if (is(typeof(iface!T)))
             enum hasAccept = true;
         else {
             private static void func() {
                 iface(T());
             }
         }
     }

Atila

On Friday, 4 April 2014 at 10:55:53 UTC, Artur Skawina wrote:
 On 04/04/14 11:22, Atila Neves wrote:
     enum hasAccept(T) = is(typeof(() {
         auto s = T();
         auto foo = FooVisitor();
         s.accept(foo);
         auto bar = BarVisitor();
         s.accept(bar);
     }));
 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.
template hasAccept(T) { static iface(CT)(CT t) { auto foo = FooVisitor(); t.accept(foo); auto bar = BarVisitor(); t.accept(bar); } static if (is(typeof(iface!T))) enum hasAccept = true; else enum hasAccept = &iface!T; } [Should probably be split into three parts: 1) the if-definition, 2) the does-T-implement-the-if check, and 3) the T-must-implement-the-if assertion.] artur
Apr 04 2014
parent Artur Skawina <art.08.09 gmail.com> writes:
On 04/04/14 13:43, Atila Neves wrote:
 That gives me this:
     static_override.d(22): Error: variable
static_override.Struct.hasAccept!(Struct).hasAccept had semantic errors when
compiling
Apparently it's compiler version dependent; I was testing with ~2.064. Hiding the errors in this case makes no sense (compilation is going to fail anyway), so this might be a regression. artur
Apr 04 2014
prev sibling parent reply "Dicebot" <public dicebot.lv> writes:
On Friday, 4 April 2014 at 09:22:16 UTC, Atila Neves wrote:
     enum hasAccept(T) = is(typeof(() {
         auto s = T();
         auto foo = FooVisitor();
         s.accept(foo);
         auto bar = BarVisitor();
         s.accept(bar);
     }));
I have lately started to favor separated asserts as opposed to combined constraints: mixin template hasAccept(T) { static assert ( is(typeof(() { auto s = T(); })), "Can't create instance of " ~ T.stringof ); static assert ( is(typeof(FooVisitor)), "FooVisitor is not defined" ); // .. and so on } It is much less elegant and does not fix issue fundamentally but speeds up debugging for end user of the library quite a lot.
Apr 04 2014
parent "Atila Neves" <atila.neves gmail.com> writes:
That works too. I think I like the solution above better for now,
I just have to figure out how to generalise it into a template
mixin so I don't have to write it out over and over again.

Atila

On Friday, 4 April 2014 at 14:24:55 UTC, Dicebot wrote:
 On Friday, 4 April 2014 at 09:22:16 UTC, Atila Neves wrote:
    enum hasAccept(T) = is(typeof(() {
        auto s = T();
        auto foo = FooVisitor();
        s.accept(foo);
        auto bar = BarVisitor();
        s.accept(bar);
    }));
I have lately started to favor separated asserts as opposed to combined constraints: mixin template hasAccept(T) { static assert ( is(typeof(() { auto s = T(); })), "Can't create instance of " ~ T.stringof ); static assert ( is(typeof(FooVisitor)), "FooVisitor is not defined" ); // .. and so on } It is much less elegant and does not fix issue fundamentally but speeds up debugging for end user of the library quite a lot.
Apr 05 2014