On 5/16/17 9:54 AM, Stanislav Blinov wrote:
On Tuesday, 16 May 2017 at 12:27:30 UTC, Steven Schveighoffer wrote:
When we have tests using dummy lambdas, are we to expect users to
immediately extract the lambda body, parse it, and figure out what's
wrong?
This is what you have to do today. The task has already been tried by
the compiler, and the result is known by the compiler. Just have the
compiler tell you.
:) The compiler does not know what I'm checking for with that lambda. As
far as the compiler is concerned, I'm interested in whether it compiles
or not. It doesn't care what that means in the context of my constraint.
Neither should the user.
You seem to be not understanding that a hand-written message of "needs
to have member x" or something conveys the same information as the
compiler saying "Error in this line: auto test = T.x"
Sure, it's not proper English. It gets the job done, and I don't have to
instrument all my functions and template constraint helpers. I don't
need to explode compile times, or debug why my messages don't match what
actually happens.
The compiler, and by extension your hand-written error checking,
cannot know the true intention of the user.
The constraint *is* hand-written error checking. What I'm talking about
is hand-written human-readable error messages :) It's not about knowing
user intentions, it's about informing them why they made a mistake.
hand-written == hand-compiled. That is, I translated what I think this
boolean condition means into a human-readable format by hand. I could
have got it wrong. Then a nice message is useless.
All it knows is you tried to do something that isn't supported. You
have to figure out what is wrong and fix it. If that takes several
iterations, that's what it takes. There is no solution that will give
you all the answers.
Hold on a second, I think there's a mixup in terminology here. A user
(caller) of move() is not supposed to be interested in what particular
evaluation chain caused the constraint to be false. I, as an author,
declare a contract (constraint). I am not interested in user's
intentions. I am interested that I'm being called correctly. When the
user violates the contract, I must inform them they did so, by reporting
*why* their argument does not satisfy me. Not by telling *how* I figured
that out (compiler output), but by telling *what* is wrong with the
argument (human-readable error message).
The constraint tells the user why it doesn't work. There is no extra
effort required. It's human readable (I know what isInputRange!R means).
I don't need a specialized full-sentence message to understand that.
If the user does want to know how the constraint caught their mistake,
they're free to inspect what the compiler outputs.
And here is the problem. Your solution doesn't get us any closer to
that. It's actually quite painful to do this today. When I have a type
like this:
struct S
{
int foo;
}
and the hand-written error message says: "Your type must be a struct
that contains an integer property named 'foo'". How does that help me
figure out what I did wrong? We can spend all day arguing over how nice
this message is, but the truth is, what the constraint writer put in the
constraints, and how the compiler is interpreting it, may be 2 different
things. This helps nobody.
That is why (static) asserts have an optional string argument: to
display a clean an readable error message, instead of just dumping some
code on the user. The same thing, in my opinion, is needed for constraints.
If assert(x == 5) would just print "Error: x == 4" automatically, we
could eliminate most needs for a message.
You could solve this with a message, but again, this is a huge task to
undertake on ALL code in existence, rather than fixing it all to a "good
enough" degree that we don't need the messages. And it avoids the "human
compiler" that is prone to error.
In your example, the compiler would point at isMovable!S as the issue.
Not super-informative, but is all it gives to prevent huge outputs.
Then you tell it to print more information, and it would say that
false was returned when the m member of type T is being checked, at
which point you could get a stack trace of what values were at each
level of recursion. Everywhere a boolean evaluated to true in order to
get to the point where false is returned would be colored green, every
time it was false, it would be colored red, and every time a short
circuit happened, it wouldn't be colored.
Or the user could just read a string "This overload cannot be called,
because argument 1 (struct S) has a destructor and non-statically
initialized const members". No "then", no printing more information, no
stack traces. User is informed their type is wrong and *why* it is
wrong. If they disagree, if they think there's a bug in the constraint,
or if they're interested in how the constraint works, they're free to go
through all the hoops you describe.
That doesn't help me if the constraint author didn't put that message,
or the constraint author put the *wrong* message, or wrote the
constraint incorrectly.
-Steve