|
Boost : |
From: Douglas Gregor (gregod_at_[hidden])
Date: 2002-03-23 19:30:34
Hello,
STL and Boost error messages are horrific. Concept checking comes up with
slightly better error messages, but these messages still have several
disadvantages:
- Compilers print out huge instantiation stacks
- The formatting of messages is subject to the formatting of type names
- The_Messages_Tend_To_Be_Hard_To_Read
This is a problem with every library, but it is much worse in highly
templated libraries like many of those in Boost. These bad error messages are
a huge barrier to entry for newcomers, but there doesn't seem to be much we
can do within the language to make it better... so why not extend?
I've implemented a tiny extension to GCC that allows template metaprograms
two new abilities:
1) Format and print messages, warnings, and errors via the compiler's
internal diagnostic routines from a template metaprogram
2) Determine if the instantiation of a given class will cause any errors at
compile-time, but do NOT emit any error messages (this is a
compiler-supported can_instantiate type trait).
Before describing the interface, here's an example based on Boost.Function.
Take this bogus code:
class X {};
int foo(X*);
boost::function<int, float> f(&foo);
The error message looks something like this:
-----------------------------------------------------------------
../../../boost/function/function_template.hpp: In static member function
`static R boost::detail::function::function_invoker1<FunctionPtr, R,
T0>::invoke(boost::detail::function::any_pointer, T0) [with FunctionPtr =
int (*)(X*), R = int, T0 = float]':
../../../boost/function/function_template.hpp:461: instantiated from `void
boost::function1<R, T0, Policy, Mixin, Allocator>::assign_to(FunctionPtr,
boost::detail::function::function_ptr_tag) [with FunctionPtr = int (*)(X*), R
= int, T0 = float, Policy = boost::empty_function_policy, Mixin =
boost::empty_function_mixin, Allocator =
std::allocator<boost::function_base>]'
../../../boost/function/function_template.hpp:444: instantiated from `void
boost::function1<R, T0, Policy, Mixin, Allocator>::assign_to(Functor) [with
Functor = int (*)(X*), R = int, T0 = float, Policy =
boost::empty_function_policy, Mixin = boost::empty_function_mixin, Allocator
= std::allocator<boost::function_base>]'
../../../boost/function/function_template.hpp:278: instantiated from
`boost::function1<R, T0, Policy, Mixin, Allocator>::function1(Functor) [with
Functor = int (*)(X*), R = int, T0 = float, Policy =
boost::empty_function_policy, Mixin = boost::empty_function_mixin, Allocator
= std::allocator<boost::function_base>]'
../../../boost/function.hpp:475: instantiated from `boost::function<R, T1,
T2, T3, T4, T5, T6, T7, T8, T9, T10>::function(Functor) [with Functor = int
(*)(X*), R = int, T1 = float, T2 = boost::detail::function::unusable, T3 =
boost::detail::function::unusable, T4 = boost::detail::function::unusable, T5
= boost::detail::function::unusable, T6 = boost::detail::function::unusable,
T7 = boost::detail::function::unusable, T8 =
boost::detail::function::unusable, T9 = boost::detail::function::unusable,
T10 = boost::detail::function::unusable]'
bad_error_eg.cpp:29: instantiated from here
../../../boost/function/function_template.hpp:81: cannot convert `float' to
`X*
' in argument passing
-----------------------------------------------------------------
With an updated Boost.Function and this extension to GCC, the error message
becomes:
bad_error_eg.cpp:29: objects of type 'int (*)(X*)' cannot be invoked with
Boost.Function argument types '(float)'
Note a few important things: there is no instantiation stack, and there are
no additional errors before or after this two-line error message. Only
information relevant to the misuse of Boost.Function is given, and all of the
information given is determine by the _library author_, not the compiler.
If you're interested in the modifications to Boost.Function to achieve this,
please check out the `compiler_supported_error_messages' branch in CVS. The
affected files are (in order of importance):
boost/function/errors.hpp
boost/function/function_template.hpp
boost/function.hpp
(The newer error messages are enabled with the BOOST_COMPILER_HOOKS macro)
If you're interested in the interface with the compiler, read on...
-----------Emitting diagnostics------------
Diagnostics are emitted using several class templates located in the header
<ext/compiler>. These class templates are named 'print', 'warning', and
'error' to print simple messages, warnings, and errors, respectively. Each is
instantiated with 3 template arguments:
- a 'message' type
- a list of arguments (default = no arguments)
- an instantiation depth to suppress (default = 0)
A message type is just a type with a static constant character string named
'text', whose definition must be visible. For instance, the 'Hello, World!'
message would look like this:
struct HelloWorld {
static const char* text;
};
const char* HelloWorld::text = "Hello, World!";
Messages may contain three types of printf-style placeholders:
- %t denotes a type
- %v denotes a (compile-time) value (5, &foo, true, etc.)
- %s denotes a message string (e.g., another message class)
The list of arguments is a typelist containing the arguments that will
replace the printf-sytle placeholders. The 'arg' class is a typelist
constructor (i.e., 'cons' in LISP) and each element in the list is either:
- type<T> (where 'T' is the type to print for the corresponding %t)
- value<T, value> (where 'T' is the type of 'value', and 'value' is a
compile-time value; this corresponds to a '%v')
- string<T> (where 'T' is a message type to print for the corresponding
'%s')
Here's a quick example:
struct the_type_is {
static const char* text;
};
const char* the_type_is::text = "The type is: %t";
sizeof(error<the_type_is, arg<type<int> > > ); // #1
This will print an error message at the line marked #1 that reads 'The type
is: int'
--------------More advanced formatting--------------
More advanced error messages might not be easily encoded in a single string.
The same formatting constructs that are used in diagnostics can be used to
format new strings, sprintf-style. The class template 'format' has the
following interface:
template<typename Msg, typename Args = /* no arguments*/ >
struct format {
static const char* text;
};
Again, 'Msg' is a message (with a 'text' field) and Args is the set of
arguments that will be substituted for the %t, %v, and %s placeholders in the
message. This instance of format is itself a message class: the compiler
formats the message and places the result into the default initializer of
'text' so that a message can be printed. This could allow, for instance, much
more sophisticated concept checking, such as:
<some file>:<some_line> Type 'std::my_ra_iterator' does not model the
RandomAccessIterator concept because the following expressions are not
supported (for std::my_ra_iterator x and int n):
x += n
x -= n
n + x
---------------can_instantiate trait-----------------
The can_instantiate trait is relatively simple. It's interface is:
template<class _ToInstantiate>
struct can_instantiate {
// The compiler will change this value to 'true' if the template
// parameter can safely be instantiated
enum { value = false };
};
If the type '_ToInstantiate' can be instantiated, then
can_instantiate<_ToInstantiate>::value will be true, and false otherwise. And
because the instantiation of a class can trigger the instantiation of
arbitrary code segments, it allows the metaprogram to 'test compile' code
without breaking the rest of the compilation.
----------------------------------------------------
For reference, the source to the 'ext/compiler' header is at the end of the
message. The pragmas aren't part of the interface, but serve as a link with
the GCC extension. I've extended mainline GCC (3.2 prerelease) to use these
pragmas, and the code here and on the Boost CVS branch has been tested under
this modified compiler. If there is interest, I can make patches available.
I believe this is on-topic for Boost (because it is a compiler-supported
library that aids in library construction), but I apologize if it is
off-topic.
Comments would be appreciated.
Doug
#ifndef __GLIBCPP_INTERNAL_COMPILER_H
#define __GLIBCPP_INTERNAL_COMPILER_H
#pragma GCC system_header
namespace __gnu_cxx
{
template<class _Arg, class _Next = void>
struct arg {
typedef _Arg arg_type;
typedef _Next next;
};
template<class _Msg> class string {};
template<class _Type> class type {};
template<class _Type, _Type _Value> class value {};
#pragma GCC compile_time_action message
template<class _Msg, class _Next = void, int SuppressDepth = 0>
class print {};
#pragma GCC compile_time_action warning
template<class _Msg, class _Next = void, int SuppressDepth = 0>
class warning {};
#pragma GCC compile_time_action error
template<class _Msg, class _Next = void, int SuppressDepth = 0>
class error {};
#pragma GCC compile_time_action format
template<class _Msg, class _Next = void>
struct format {
static const char* text;
};
#pragma GCC compile_time_action try_compile
template<class _ToInstantiate>
struct can_instantiate {
// The compiler will change this value to 'true' if the template
// parameter can safely be instantiated
enum { value = false };
};
} // namespace __gnu_gcc
#endif /* __GLIBCPP_INTERNAL_COMPILER_H */
// Local Variables:
// mode:C++
// End:
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk