On Sun, Jan 28, 2018 at 10:53:39PM +0000, welkam via Digitalmars-d-learn wrote: > On Sunday, 28 January 2018 at 20:42:52 UTC, Jonathan M Davis wrote: [...] > > However, you're not going to get an error message that says anything > > like "the arguments aren't the same type." The compiler doesn't > > understand what the template constraint means in "human terms." It > > just knows whether it's true or false, and in this case, if you > > provide arguments that don't have a common type that they implicitly > > convert to, then the template constraint will fail. But ultimately, > > you're going to have to read the template constraint and figure out > > why the arguments are failing. [...]
Well, that's not *quite* true. The compiler *does* have enough information to be able evaluate the constraints, for one. So in theory it *should* be able to identify which constraint(s) failed. Some time ago there was a push to refactor Phobos sig constraints in CNF (conjunctive normal form), which would simplify this somewhat: if a function has sig constraints X, Y and Z, and Y evaluated to false, then the compiler could print condition Y as the cause of failure. Furthermore, the reason for the perceived unhelpful message is more subtle. It's because a sig constraint amounts to a limitation in scope of a particular overload, effectively saying "this overload only applies if conditions X, Y and Z are true". So a failure in one or more sig constraints isn't necessarily a *problem* per se; it just means that particular overload declines instantiation to the rest of the overload set. Only if the failure ultimately leads to a failure to match *all* overloads, can we say for sure that the failed constraint was the "cause" of the mismatch. And this is where things get tricky, because if you have an overload of, say, 10 members, then obviously a failure to find a match means that *every* overload had its sig constraints fail one way or another. But without being able to read the programmer's mind, the compiler has no idea which overload was the *intended* target of the function call; so it would not be able to tell which failed sig constraint was the most relevant to the user. All >=10 failed conditions would have to be displayed, and it would be up to the user to decide which one is the relevant one. Of course, even in spite of this, displaying the failed conditions is still better than nothing. If the compiler assumes CNF on sig constraints, then it can list, along with each candidate overload, the failing term(s) in the CNF for that particular overload as the reason that overload wasn't chosen. For example, the output could be something like: Error: unable to match func() with argument type X; candidates are: std/range/package.d(100): auto func(R)(R r) if (isInputRange!R && is(ElementType!R == char): condition is(ElementType!R == char) failed std/range/package.d(200: auto func(R)(R r) if (isForwardRange!R && is(ElementType!R : int): condition isForwardRange!R failed std/range/package.d(300): auto func(E)(E[] arr): X does not match parameter type E[] You'd still have to parse through each listed overload, but at least the compiler would tell you which sig constraint failed. IME, this would be helpful, because sometimes it's not at all obvious which sig constraint failed, especially if the sig constraints call upon other templates to test the incoming type (e.g., isInputRange, which contains a whole bunch of tests, any of which could fail and it's not always obvious which ones), and I wind up having to parse through *all* sig constraints of *all* overloads just to figure out which one was the real cause. > I would not complain if there were multiple functions just like error > said. But the set contains only one making it not a set anymore and > it says nothing about constraints. [...] This topic has come up again numerous times, and I think *some* improvement on this front is necessary. One improvement is to decrease our reliance on overloads, and to use more permissive sig constraints with static ifs inside the function body that can provide a more helpful error message. Not to mention, some of the current Phobos overloads are divided based on implementation details user code shouldn't need to know about, rather than *logical* overload boundaries, for example: // handle the case when S is a string auto func(R,S)(R haystack, S needle) if (isInputRange!R && isSomeString!S) ... // handle the case when S is an array but not a string auto func(R,S)(R haystack, S needle) if (isInputRange!R && !isSomeString!S && isArray!S) ... This is leaking implementation details into the sig constraints; it really should be written this way instead: auto func(R,S)(R haystack, S needle) if (isInputRange!R) { static if (isSomeString!S) ... // handle the case where S is a string else static if (isArray!S) ... // handle the case where S is an array but not a string else // Helpful message if implementation doesn't // support the incoming type static assert(0, "S must be either a string or an array"); } This way, there's (1) a smaller number of overloads the user needs to parse through when an error occurs; (2) when an error occurs, it's obvious that the failing condition is isInputRange!R: the user only needs to figure out 1 sig constraint as opposed to 4 to find the problem; and (3) the else-clause of an internal static if can be used to provide more user-friendly error messages, if the argument is an input range, but not of a kind the current implementation supports. I'd even venture to say that this applies not just to Phobos but to template code in general. Personally, I go by the rule that the sig constraints should be general, and should document what the user would logically understand the function parameters ought to be (i.e., the parameter must be an input range). Anything else, like handling various specializations (do X if the element type is char, do Y if it's a POD, etc.) should be done inside the function body, away from the public-facing sigs. If the current implementation doesn't handle all of the types that the function logically should, then the else-clause of the internal static if can be used to provide a more user-friendly error message explaining why the passed-in type doesn't work. T -- We are in class, we are supposed to be learning, we have a teacher... Is it too much that I expect him to teach me??? -- RL