DMD 2.086 live, GCC 9 with D support formally released, DConf coming soon, links to posts on builder pattern and disallowing implicit conversions with templates, and 2d array op overloads

Posted 2019-05-06

D 2.086 came out last week. The big news this week though is that DConf is happening in London. It is supposed to be livestreamed and recorded, but I haven't heard any details as to where yet.

GCC 9's stable release came out, which includes D! What this means is D is another language that can be enabled when building gcc.... which happens to bootstrap further D updates later, since gcc and now compile D's D code for later frontends. gdc/gcc should now track new D releases more frequently than it has recently.

Meanwhile, I wrote a couple posts on the forums that I will link to for fun tips, one on automated builder pattern for named function parameter emulation, and one on struct construction, alias this, and disallowing implicit conversions via templates, and also one talking about operator overloading.

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

Tip of the Week

Builder Pattern

This is copy/pasted from a post I made on the forums.

If you are doing function parameters, there are two kinda fun things you can do. (Personally, I kinda prefer to just do hand-written builder patters, nicer to document, often easier to read, but this is D, so let's go nuts!)

First, this is an automatically generated struct with members corresponding to function parameters:

1 
2 void foo(int a, string cool = "low temperature", int[] c = [1, 2, 3]) {
3 	import std.stdio;
4 	writeln("a = ", a);
5 	writeln("cool = ", cool);
6 	writeln("c = ", c);
7 }
8 
9 // this works for free functions, but not delegates, function pointers, or other callable objects
10 // it also will not automatically call a method, but you can build parameters for it.
11 struct ParamsFor(F...) if(F.length == 1) {
12 	static if(is(typeof(F[0]) Parameters == __parameters)) {
13 		static foreach(idx, _; Parameters) {
14 			static if(__traits(compiles, ((Parameters[idx .. idx + 1] i) => i[0])()))
15 			mixin("
16 				Parameters[idx .. idx + 1][0] // type
17 				"~__traits(identifier, Parameters[idx .. idx + 1])~" // name
18 				= ((Parameters[idx .. idx + 1] i) => i[0])() // initial value
19 			;");
20 			else
21 			mixin("
22 				Parameters[idx .. idx + 1][0] // type
23 				"~__traits(identifier, Parameters[idx .. idx + 1])~" // name
24 				// no initial value
25 			;");
26 		}
27 	} else static assert(0, typeof(F[0]).stringof ~ " is not a plain callable");
28 
29 	auto opCall()() {
30 		static if(__traits(compiles, F[0](this.tupleof)))
31 			return F[0](this.tupleof);
32 		else static assert(0, __traits(identifier, F[0]) ~ " is not callable this way since it needs a `this` object, do it yourself on the outside with obj.method(params.tupleof)");
33 	}
34 }
35 
36 class Test {
37 	void foo(int a, int b = 10, int c = 20) {
38 		import std.stdio;
39 		writeln(a, " ", b, " ", c);
40 	}
41 }
42 
43 void main() {
44 	ParamsFor!foo params;
45 
46 	params.c = [4,5,6];
47 
48 	params(); // calls foo(params.tupleof) for you
49 
50 
51 	ParamsFor!(Test.foo) p2;
52 	p2.c = 30;
53 	auto f = new Test();
54 
55 	//p2(); // will static assert cuz of this
56 
57 	f.foo(p2.tupleof); // use this instead
58 }

But there, required parameters can be left out too - you don't have to set anything. (It also doesn't work with const params and other such troubles, but that really complicates this idea - and is part of why I prefer a hand-written builder thing, so you can handle all those details explicitly.)

We can solve that with a constructor. Right below the first static if in the example, add:

1 static if(!__traits(compiles, ((Parameters _) {}) () )) {
2 	@disable this();
3 	this(Parameters params) {
4 		this.tupleof = params;
5 	}
6 }

And now you get an obscure error if you don't specific parameters when creating the Params object. But.... eh I don't love it.

Regardless, still though, this stuff is kinda cool. And if you combine with the with statement:

1 void main() {
2         // this assumes the version with the constructor
3         // but if you didn't add that code, just remove
4         // the 5 and thus ParamsFor!foo()
5         with(ParamsFor!foo(5)) {
6                 c = [4,5,6]; // set the param c...
7                 opCall(); // call the function
8         }
9 }

so yeah, kinda cool.

Struct construction and implicit conversions

Another bit of copy/pasta here, from this post. It was in reply to someone asking about disabling bool to int conversion in a custom struct, and is only lightly edited.

First, toensure we are on the same page for vocabulary: it is *explicitly* constructed with MyStruct a = something;, it just happens to share the = syntax with assignment... but since it is a new variable being declared here, with its type given, this is explicit construction.

And this construction can occur in a a = x; context too, without a declaration, if it happens in an aggregate constructor.

1 MyStruct a = x; // explicit construction, but with = syntax
2 class A {
3    MyStruct a;
4    this() {
5        a = x; // considered explicit construction!
6    }
7 }

But:

1 void foo(MyStruct a) {}
2 
3 foo(MyStruct(x)); // explicit construction
4 
5 foo(not_a_struct); // this is implicit construction, and banned by D

And meanwhile:

1 MyStruct a;
2 
3 a = x; // now this is assignment
4 
5 class A {
6    MyStruct a;
7    void foo() {
8        a = x; // this is also assignment
9    }
10 }

The syntax needs to be taken in context to know if it is assignment or construction.

If it is construction, it calls this(rhs) {} function, if assignment, it calls opAssign(rhs) {} function.

But, once the compiler has decided to call that function, it will allow implicit conversion to its arguments. And that's what you saw: implicit conversion to the necessary type for an explicit construction.

So, it is the implicit conversion to our type we want to prohibit. But, remember that implicit construction, the function call thing we mentioned thing, is banned. Which brings us to a potential solution.

Change "ID" from an alias to a struct of some sort. I've been trying to find similar issues, and I saw once suggested that a person could make a struct with one member and conversions to and from different types. I've also seen "alias X this;" a lot. But my main issues is stopping these conversions, and everything I've seen is about enabling automatic conversion. Ideally, I would have something that's convertible TO a uint when needed, but can't be converted FROM other data types.

This is your answer (though keep reading, I do present another option at the end of this email too that you might like).

struct ID {
    uint handle;
}

And then, if you must allow it to convert to uint, do:

1 struct ID {
2     uint handle;
3     alias handle this;
4     // and then optionally disable other functions
5     // since the alias this now enables ALL uint ops...
6 }

or if you want it to only be visible as a uint, but not modifiable as one:

1 struct ID {
2    private uint handle_;
3    @property uint handle() { return handle_; }
4    alias handle this; // now aliased to a property getter
5    // so it won't allow modification through that/
6 }

Which is probably the best medium of what you want.

Let's talk about why this works. Remember my example before:

void foo(MyStruct a) {}
foo(MyStruct(x)); // explicit construction
foo(not_a_struct); // this is implicit construction, and banned by D

And what the construction is rewritten into:

MyStruct a = x; // becomes auto a = MyStruct.this(x);

alias this works as implicit conversion *from* the struct to the thing. Specifically, given:

MyStruct a;

If, a.something does NOT compile, then it is rewritten into a.alias_this.something instead, and if that compiles, that code is generated: it just sticks the alias_this member in the middle automatically.

It will *only* ever do this if: 1) you already have an existing MyStruct and 2) something will not automatically work with MyStruct directly, but will work with MyStruct.alias_this.

#1 is very important: alias this is not used for construction, in any of the forms I described above. It may be used for assignment, but remember, not all uses of = are considered assignment.

Let's go back to your code, but using a struct instead.

1 
2 struct ID {
3     uint handle_;
4     @property uint handle() { return handle_; }
5     alias handle this;
6 }
7 
8 struct Data
9 {
10 	ID id;
11 	this(ID id)
12 	{
13 		this.id = id;
14 	}
15 }
16 
17 // Forgot to refactor a function to return its
18 // loaded data, rather than a success/fail bool
19 bool load_data()
20 {
21 	// some processing
22 	return true;
23 }
24 
25 int main()
26 {
27 	// Very obviously a bug,
28 	// but it still compiles
29 	Data d = load_data();
30 	return 0;
31 }
kk.d(31): Error: constructor kk.Data.this(ID id) is not callable using argument types (bool)
kk.d(31):        cannot pass argument load_data() of type bool to parameter ID id

Yay, an error! What happens here?

Data d = load_data();

rewritten into

Data d = Data.this(load_data() /* of type bool */);

Data.this requires an ID struct... but D doesn't do implicit construction for a function arg, so it doesn't even look at the alias this. All good.

What if you *wanted* an ID from that bool?

Data d = ID(load_data());

That compiles, since you are now explicitly constructing it, and it does the bool -> uint thing. But meh, you said ID() so you should expect that. But, what if we want something even more exact? Let's make a constructor for ID.

This might be the answer you want without the other struct too, since you can put this anywhere to get very struct. Behold:

1 struct ID {
2     uint handle_;
3 
4     @disable this(U)(U u);
5     this(U : U)(U u) if(is(U == uint)) {
6        handle_ = u;
7     }
8 
9     @property uint handle() { return handle_; }
10     alias handle this;
11 }

That stuff in the middle is new. First, it disables generic constructors.

Then it enables one specialized on itself and uses template constraints - which work strictly on the input, with no implicit conversion at all (unless you want it - is(U : item) learn more about is allows implicit conversion there and you can filter through).

Now you can get quite strict. Given that ID struct:

Data d = ID(load_data()); // will not compile!
Data d2 = ID(0u); // this one will
Data d3 = ID(0); // will not compile!

The difference between 2 and 3 is just that u... it is strict even on signed vs unsigned for these calls.

(thanks to Walter for this general pattern. to learn more, I wrote about this more back in 2016: http://arsdnet.net/this-week-in-d/2016-sep-04.html )

There's a lot of options here, lots of control if you want to write your own structs and a bit more code to disable stuff.

2d operator overloading

I will just link this post: I wrote about how to do a multi-dimensional array, listing the various overloadable functions to hopefully clarify some of the official language docs.