December 02, 2013 opDispatch + alias this intervene compilation? | ||||
---|---|---|---|---|
| ||||
Consider this code: ---- struct VecN(T, const uint Dim) { /** * Stores the vector values. */ T[Dim] values = 0; /// Alias alias values this; /** * CTor */ this(float[Dim] values) { this.values = values; } /** * opDispatch for x, y, z, w, u, v components */ @property T opDispatch(string str)() const pure nothrow { //writeln(str); // [1] static if (str[0] == 'x') return this.values[0]; else static if (str[0] == 'y') return this.values[1]; else static if (str[0] == 'z') return this.values[2]; else static if (str[0] == 'w') return this.values[3]; else static if (str[0] == 'u') return this.values[4]; else static if (str[0] == 'v') return this.values[5]; else return 0; } } alias vec2f = VecN!(float, 2); void main() { vec2f[] arr; arr ~= vec2f([1, 2]); arr ~= vec2f([3, 4]); import std.stdio; writefln("ptr = %x", arr.ptr); } ---- It will fail with: ---- object.Exception@/opt/compilers/dmd2/include/std/format.d(2245): Incorrect format specifier for range: %x ---- But if you comment out [1] it works as expected. I'm sure it is a bug, but I've no idea how to name it. |
December 02, 2013 Re: opDispatch + alias this intervene compilation? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Namespace | 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.
|
Copyright © 1999-2021 by the D Language Foundation