
On 11/26/2011 9:22 PM, Ábel Sinkovics wrote:
Hi,
The problem is that errors point to code that is deep within the library's implementation, or worse, the implementations of helper libraries used by the library. To understand the error, the user has to look at the library's implementation. The user shouldn't have to do that - understanding the implementation of a library should not be a prerequisite for using it.
If you have any specific suggestions about what compilers could do to turn these errors from deep within the library implementation, into errors that do not require knowing anything about the library implementation, I would like to hear it, and I'm sure so would compiler writers.
Otherwise, you have to accept that library writers face a trade-off between the user-friendliness of error messages, and the expressiveness, terseness, and power obtained by extensive use of advanced techniques such as template metaprogramming. There is no one right answer to this tradeoff, and it is good for users to have different alternatives available to them.
We've been experimenting with returning error messages from TMP libraries that make sense in the domain of the library. Our approach is returning a class describing the error when a metafunction is called with invalid arguments (a pretty-printer can display that information later in a human-readable way). The assumption is that this error information is returned by a metafunction deeply inside the TMP library and need to be propagated out (maybe changed a bit along the way to make more sense to the user). For this we've used monads, using which we could simulate exceptions being thrown at compile-time. Using monads increases the complexity of the TMP library, however, most of it can be hidden by another library. We've been working on such a library (not in Boost).
What TMP library authors can do is wrapping the body of every metafunction with a template that "adds" error propagation to it. For example instead of:
template <class A, class B, class C> struct some_metafunction_in_the_library : f<g<A, C>, h<B>, A> {};
one can write
template <class A, class B, class C> struct some_metafunction_in_the_library : try_<f<g<A, C>, h<B>, A> > {};
in which case if f, g or h returns an error, the same error is returned by some_metafunction_in_the_library instead of breaking the compilation with a cryptic error message. At the top level it can be pretty-printed by a test harness (eg. simple binary built to pretty-print it or by a TMP unit testing framework). A downside of using it is that it makes compilation slower (see http://plcportal.inf.elte.hu/en/publications/TechnicalReports/monad-tr.pdf for measurements).
So far we've only been using it for improving error messages coming from template metafunctions. Cases where they can't be easily and clearly separated from runtime code haven't been addressed yet.
This solution is part of a library implementing monads in C++ template metaprogramming. Documentation: http://abel.web.elte.hu/mpllibs/metamonad/index.html Source code: https://github.com/sabel83/mpllibs/tree/master/libs/metamonad
Note that there are only a few examples so far.
I've been reading the docs. This is very clever! I wonder how it applies to expression templates with runtime code or Fusion with runtime components? A simple example would be a template plus function: template <typename A, typename B> auto plus(A const& a, B const& b) -> decltype(a+b) {return a+b;} The typical problem is an error if a+b is not allowed (e.g. a is an int but b is a std::vector). I don't see how your solution will help more than static_assert or better yet, Boost concepts. Regards, -- Joel de Guzman http://www.boostpro.com http://boost-spirit.com