www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - opDispatch + alias this intervene compilation?

reply "Namespace" <rswhite4 googlemail.com> writes:
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.
Dec 02 2013
parent "Adam D. Ruppe" <destructionator gmail.com> writes:
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.
Dec 02 2013