
Hi Ben,
This submission would make it possible to write a complete set of unit tests for meta-programs, to test both the positive, compiling statements, and the negative, non-compiling statements. These tests will all compile, and the negative tests can throw an exception instead of failing to compile.
By negative tests do you mean compile-time predicates that return false?
The submission source is available at https://github.com/icaretaker/Metatest. The provided examples are based on the factorial metafunction from Chapter 8 of "C++ Template Metaprogramming" by David Abrahams and Aleksey Gurtovoy - http://www.boostpro.com/mplbook/. Chapter 8 explains the rational behind the BOOST_MPL_ASSERT_* macros. This submission complements these macros by allowing the writing of regression unit tests, to ensure the user will encounter the mpl formatted compiler error, if the library types are incorrectly instantiated.
Your factorial (example_boosttest_factorial.cpp) example uses a compile-time assertion to check the validity of the argument of factorial. In my understanding of what you wrote (and how your example uses it), you intend to throw a runtime exception from the default constructor of the factorial class. What a metafunction such as factorial could do for handling errors is throwing a "compile-time exception". This is something I've implemented in an other library (metamonad) in my mpllibs repository. This is a bunch of tools simulating exceptions at compile-time. One can "throw" and "catch" them. The "exception" that is "thrown" can contain a class describing the problem itself in a meaningful way. These simulated exceptions can be propagated in the template metafunction call-chain. The fact that an "exception" is propagated out of a metaprogram doesn't break the compilation on its own. It generates errors when the metaprogrammer tries to use it as it was the real result. When the metafunction is used in a compile-time predicate and an exception is propagated out, metatest can display it or add it to Boost.Test (including the meaningful description of the problem) using pretty-printing. When a metaprogram is used in real code and an "exception" is propagated out of it, it will most likely not be usable in the context the metaprogrammer is trying to use it in, thus it generates a compilation error. What I've found useful in such situations is compiling the problematic metaprogram on its own and display it with the pretty-printing solution of metatest. Simulated exceptions are implemented using monads. A drawback of this is that every metafunction in the call-chain has to be prepared for propagating exceptions explicitly. However, metamonad provides a template class (try_) adding this to existing metafunctions. It can be used the following way: template </* args */> struct metafunction_not_prepared_for_exception_propagation : /* body */ {}; template </* args */> struct metafunction_prepared_for_exception_propagation : metatest::try_< /* body */ > {}; Your example: template <class N> struct factorial : mpl::eval_if< mpl::less_equal<N, mpl::int_<0> > , mpl::int_<1> , mpl::times<N, factorial<typename mpl::prior<N>::type> > >::type { BOOST_METATEST((mpl::greater_equal<N, mpl::int_<0> >)); }; BOOST_AUTO_TEST_CASE(failing_negative_homemade_framework) { bool caught = false; try { factorial<mpl::int_<-1> > factneg1; } catch (metatest_exception & ee) { caught = true; } BOOST_CHECK_EQUAL(caught, true); } could be implemented using metamonad's compile-time exceptions the following way: // class describing the problem struct negative_factorial_argument {}; // adding pretty-printing support MPLLIBS_DEFINE_TO_STREAM_FOR_TYPE( negative_factorial_argument, "The factorial metafunction has been called with a negative argument." ) template <class N> struct factorial : mpl::eval_if< typename mpl::greater_equal<N, mpl::int_<0> >::type, mpl::eval_if< mpl::less_equal<N, mpl::int_<0> >, mpl::int_<1>, mpl::times<N, factorial<typename mpl::prior<N>::type> > >, metamonad::throw_<negative_factorial_argument>
{}; template <class NullaryMetafunction> struct no_throw : metamonad::do_try< NullaryMetafunction, metamonad::do_return<mpl::true_>
{}; MPLLIBS_DEFINE_TO_STREAM_FOR_TEMPLATE(1, no_throw, "no_throw") BOOST_AUTO_TEST_CASE(failing_negative_homemade_framework) { metatest::meta_check< no_throw<factorial<mpl::int_<-1> > >
(MPLLIBS_HERE); }
No runtime exceptions are used - meta_check passes the pretty-printed "compile-time exception" to a runtime unit testing framework (Boost.Test). The implementation of these template functions is simple, and one can easily write similar functions to support other unit testing frameworks.
BOOST_METATEST(pred) BOOST_METATEST_NOT(pred) BOOST_METATEST_RELATION(x, rel, y) BOOST_METATEST_MSG(cond, msg, types)
If you use macros to do assertions and your predicates contain syntax errors, the error messages will point to the macro call, not to the exact location of the error. By using template functions for assertions (meta_warn, meta_check, meta_require in my metatest implementation - see my other mail metatest interface update...) you don't hide the real location of the code from the compiler. What is the benefit of having BOOST_METATEST_RELATION, METATEST_NOT macros? For example why is using BOOST_METATEST_NOT(pred) better than using BOOST_METATEST(lazy_not<pred>) ? Regards, Abel