
Hi! <caveat> I've followed some discussions on adding Concepts to the language from a distance, but I'm not completely familiar with all the details. Take my comments with a grain of salt </caveat> Paul Mensonides wrote:
Well, for a simple scenario:
template<class T> void f(T x) { return x; }
template<class T> void g(T x) { return f(x); }
If I add concept requirements to f and g, the set of requirements for g has to include the set of requirements of f and f itself. If I add another function:
template<class T> void h(T x) { return g(x); }
Its set of requirements must include the set of requirements from g and g itself. So far, for h, that set includes CopyConstructible, the ability to call f, and the ability to call g.
That's what I'm referring to when I say "bubbling of implementation detail." Note, in particular, that CopyConstructible is not sufficient as a sole requirement of g or h. f is needed in the requirements of g, and both f and g are needed in the requirements of h because both are bound during the second phase--which could result in (e.g.) an ambiguity. You need to raise that (e.g.) ambiguity to the top-level instantiation (or concept check immediately prior to instantiation) in order to avoid the two-megabyte error messages (whether they be in regular templates or in concept checks).
It is my understanding that requirements are to be maintained by the developer. This only follows existing practice with, e.g., enable_if, static_asserts and tag dispatching. It is still up to the developer to keep those requirements accurate and up-to-date. Otherwise the compiler will "fallback" to the two megabyte error message. In my view, the Concepts approach, because it formalizes syntax and semantics, has the added benefit that it eases automated testing of the requirements. I'm not sure how much the Concept checks add to what is already possible in the current language, but having an explicit and unambiguous syntax to do the same things is better than the hacks being used today.
Something like CopyConstructible is fairly innocuous as an implementation detail, the f and g are not--particular the requirement for f in h. Such bubbling makes it virtual impossible to let those calls be resolved during the second phase (without some sort of concept requirement union mechanism either explicit or implicit).
From my experience with Spirit, for example, you'll get tons of errors from fairly small mistakes. I suppose this is what you'd call "bubbling of implementation details" and it is the current state of affairs. The situation could be improved today by adding Concept check hacks or it can be improved tomorrow with proper Concept checks. Either way, the burden will be on the developers of Spirit to keep those checks accurate and to avoid ambiguity and corner cases in the library code.
Concepts provide separate type-checking, so the error will show up in one of two places:
1) In the definition of __introsort_loop(), before it is instantiated, if there is no suitable operator<. No instantiation stack needed, because there's no instantiation yet.
2) In the user code, where the user actually made the mistake. This error will say that the requirement for, e.g., LessThanComparable is not satisfied by the types involved. No instantiation stack needed, because we haven't tried to instantiate sort().
What I fail to understand is why this is just LessThanComparable here instead of all of LessThanComparable, __introsort_loop, __introsort, __sort, and sort. All of those can fail to be bound during the second phase--even if they are namespace qualified--because of overloading.
That would be because, as the developer you take great care to ensure that you respect the stated requirements so your implementation details don't pop out of the interface. Even though the user could intentionally (or not) break things by overloading implementation functions or, god forbid, a library bug could creep in, there will always be plenty of ways to shoot one self in the foot. But, templates or no templates, the developer is responsible for hiding and maintaining implementation details. Taking your example above, if h needs to invoke g(x) generically then, that could be its Concept requirement. In that case, the compiler would ensure such invocation is possible and that x meets the requirements for g. I further suppose all this occurs while deciding if h is a viable function, and before actually trying to fully instantiate and compile it. If, on the other hand, you already know (and want to enforce) that the only effective requirement is that T be CopyConstrutible you could specify that in the requirements instead. Best regards, João Abecasis