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."

Reply via email to