On Monday, 15 May 2017 at 15:30:38 UTC, Steven Schveighoffer
wrote:
Imagine also a constraint like isInputRange!R. This basically
attempts to compile a dummy lambda. How would one handle this
in user-code?
Let's look at something more practical than my initial example,
even if less involved than isInputRange. In a discussion not long
ago it was brought up that a destructive variant of move()
violates const, and this was the response:
http://forum.dlang.org/post/odlv7q$16dr$1...@digitalmars.com
So let me try implementing the "should fail in all cases" bit.
enum bool isMovable(T) = {
import std.traits;
static if (!isMutable!T)
return false;
static if (is(T == struct) &&
(hasElaborateDestructor!T ||
hasElaborateCopyConstructor!T)) {
foreach (m; T.init.tupleof) {
static if (!isMovable!(typeof(m)) && (m == m.init)) {
return false;
}
}
return true;
} else
return true;
}();
Not exactly a one-liner. There are several checks to be made:
- if the type itself is const/immutable, it can't be moved (can't
write to const).
- if it's a struct, each member has to be checked. We can't do
this check with isAssignable, since assignment might be redefined.
- if a member is const/immutable, we should check it's init
value: if it differs from default, no constructor can change it,
so it's "safe" to move destructively (provided a more involved
implementation of move() than it is at the moment).
- maybe one or two cases that I forgot
Note that the compiler doesn't deal in the logic listed above. It
deals in statements and expressions written inside that lambda.
But as I'm writing it, I already possess all the information
required to convey the description of failure in a human-readable
manner. What I don't have is the means to present that
information.
And now:
T move(T)(ref T src) if (isMovable!T) { /*...*/ }
void move(T)(ref T src, ref T dst) if (isMovable!T) { /*...*/ }
struct S {
const int value; // no default initialization, we initialize
in ctor
this(int v) { value = v; }
~this() {} // "elaborate" dtor, we should only move this
type destructively
}
S a;
auto b = move(a);
All I realistically would get from the compiler is that it can't
resolve the overload, and, perhaps with some improvement in the
compiler, the line in the lambda that returned false, no more.
Something along the lines of:
static if (!isMovable!(typeof(m)) && (m == m.init)) {
return false;
^
|
Even though this looks a tiny bit better than what we have now,
it actually isn't. A user will have to look at the code of that
lambda and parse it mentally in order to understand what went
wrong. Whereas I could simply include actual descriptive text like
"S cannot be moved because it has a destructor and uninitialized
const members."