opDispatch + alias this intervene compilation?

Adam D. Ruppe destructionator at gmail.com
Mon Dec 2 07:48:37 PST 2013


On Monday, 2 December 2013 at 15:25:13 UTC, Namespace wrote:
> I'm sure it is a bug, but I've no idea how to name it.

Not really a bug, but surprising if you've never seen it before. 
The problem is that writeln() has different specializations if 
the argument is an input range.

writeln(my_input_range); // prints the contents of the range


How does it check if it is an input range? 
std.range.isInputRange!T. What's that implementation?

     enum bool isInputRange = is(typeof(
     (inout int = 0)
     {
         R r = void;       // can define a range object
         if (r.empty) {}   // can test for empty
         r.popFront();     // can invoke popFront()
         auto h = r.front; // can get the front of the range
     }));


It tries to see if the three functions, empty, popFront, and 
front will compile, and that front returns something. Let's go 
back to your code. What happens if you replace R with a vec2f?

vec2f r = void; // ok, can be declared
if(r.empty) {} // ok, calls r.opDispatch!"empty"
r.popFront(); // ok, calls r.opDispatch!"popFront"
auto h = r.front; // ok, calls r.opDispatch!"front", which 
returns a float


writeln thinks your vector is an input range of floats! Since 
input ranges of floats can't be printed as hex, it throws the 
exception with %x. If you tried %s, it would print out an endless 
amount of zeros, because r.empty() is returning zero, which 
converts to false in if(empty).

Put a pragma(msg) in your opDispatch and you can see it being 
instantiated for this.

Why then does putting in the writeln() prevent this? Because you 
didn't import std.stdio! So opDispatch fails to compile with 
"undefined identifier writeln", but the failure is silenced by 
the is(typeof()) check in isInputRange. So it doesn't pass as a 
range and doesn't tell you that either. (If it gave an error for 
every template constraint it failed, you'd be spammed out of 
control.)



hmm though, why is arr.ptr doing this? I can only imagine writeln 
is trying to dereference it - *arr.ptr has type of vec2f, so then 
it tries to print that and triggers the input range check. I have 
to admit that's a wee bit surprising to me too, but again, hard 
to say it is technically a bug since referencing struct pointers 
is fairly useful when writing them.



So, a few fixes here: to just print the pointer without writeln 
attempting to print the contents, cast it to void*:

         writefln("ptr = %x", cast(void*) arr.ptr); // always 
prints address


To prevent your opDispatch from incorrectly triggering the 
duck-typing checks for isInputRange, put a constraint on it:

         T opDispatch(string str)() const pure nothrow if(str != 
"popFront") {


Having been bit by this more than once, every time I write 
opDispatch, I put that != "popFront" constraint on it. It is 
usually needed.

Alternatively, you could write a helper function that does:

foreach(s; str)
   if(s < 'x' || s > 'v') return false; // not a valid vector 
component
return true; // checks out

to be a bit more strict. This is probably ideal, perhaps you can 
statically make sure it is in range too; e.g. use s > 'y' instead 
of v if there's only two components.

And, of course, if you want the writeln to actually compile in 
there, don't forget to import std.stdio in that function too.


More information about the Digitalmars-d-learn mailing list