Expressive C++: Why Template Errors Suck and What You Can Do About It

This entry is part of a series, Expressive C++»

Welcome to part 3 of Expressive C++, a series of articles devoted Embedded Domain-Specific Languages (EDSLs)1 and Boost.Proto, a library for implementing them in C++. The title of this article is intentionally provocative to give me the creative license I need to get this righteous rage out of my system, lay some much-deserved blame, and—after my catharsis—offer some constructive suggestions for improving the situation. You might be surprised at where I direct my ire, and also pleased to know that whether you’re a library author or a user, there are things you can do to help to improve the situation.

Eventually, we’ll bring the discussion back to EDSLs and apply my recommendations to the simple string formatting routine we developed in the last installment. By the end of the article, you’ll know how to syntax-check an EDSL by defining its grammar, validate that an expression matches the grammar, and issue a short and meaningful diagnostic if it doesn’t.

Template Errors: A Sad State Of Affairs

Breaking news from the C++ meta-verse2:

TEMPLATE ERROR MESSAGES ARE TERRIBLE!

<yawn>. That’s no news to anyone who’s used C++ in the last 10 years or so. Even simple misuses of template libraries can lead to 100′s of Kb of compiler spew. Who’s to blame? Take your pick: library authors, compiler vendors, or the C++ standardization committee? They’ve each felt their share of the heat. The key selling point of C++0x concepts (R.I.P.) was improved template errors. And one of the key selling points of clang, an exciting, new C/C++ compiler in active development, is better error messages. But in my personal experience as a library developer, I believe this problem begins at home: with poorly designed and implemented template libraries.

Library techniques for improved compile-time error detection and reporting have existed for a while, but they’re not commonly known or widely used.3 If folks only knew how much better the world would be if these techniques were consistently applied, we wouldn’t settle for 100′s of Kbs of compiler spew. We’d be outraged.

If I’m not being clear enough, let me say it explicitly, and in a way that’s likely to raise a few eyebrows:

Bad template errors are library bugs and should be reported as such.

The implication for library users is simple: stop cursing the darkess and start cursing library authors. Well, don’t curse them because they might be me. File bugs instead. Yes, really. (And if you just can’t wait for the bugs to be fixed, switch to clang or install STLFilt.)

What are the implications for library writers? What could a library author possibly do to fix these so-called “bugs”? And hey, why are bad template errors endemic in the first place? Simply put: a total lack of parameter validation.

Remedial Software 101

When you first starting writing code, someone probably told you how important it is to validate parameters at (runtime) API boundaries. Null pointers, out-of-bounds indices, incorrectly escaped URLs—if you fail to check for them, you’ll end up with runtime bugs that hackers can exploit. Any programmer worth his salt will tell you this.

But when those same programmers sit down to write a template, many tend to forget this very basic advice and blithely accept user-supplied types without doing any parameter checking at all. The result is a car wreck of epic proportions.

Let’s take the Boost.Spirit example from the Intro and modify it slightly:

1
2
3
4
5
6
7
8
9
10
11
#include <boost/spirit/home/qi.hpp>
 
int main()
{
    using namespace boost::spirit::qi;
    rule<char const *> expression, term, factor;
 
    expression  = term >> *( ( '+' >> term ) | ( '-' >> term ) ) ;
    term        = factor >> *( ( '*' >> ~factor ) | ( '/' >> factor ) ) ;
    factor      = uint_ | '(' >> expression >> ')' | '-' >> factor ;
}

Can you spot the typo in the code? Answer» The resulting 160 Kb of compiler spew is enough to make a sane programmer run screaming4:

In file included from /home/Eric/boost/org/trunk/boost/spir it/home/qi/char.hpp:14:0, from /home/Eric/boost/org/trunk/boost/spir it/home/qi.hpp:17, from main.cpp:1: /home/Eric/boost/org/trunk/boost/spirit/home/qi/char/char_p arser.hpp: In instantiation of ‘boost::spirit::qi::make_com posite<boost::proto::tag::complement, boost::fusion::cons<b oost::spirit::qi::reference<const boost::spirit::qi::rule<c onst char*> >, boost::fusion::nil>, boost::fusion::unused_t ype, void>’: /home/Eric/boost/org/trunk/boost/spirit/home/qi/meta_compil er.hpp:103:13: instantiated from ...

<snip enormous template instantiation backtrace>

/home/Eric/boost/org/trunk/boost/mpl/if.hpp:70:41: error: ‘ value’ is not a member of ‘boost::spirit::traits::has_no_un used<boost::fusion::transform_view<boost::fusion::cons<boos t::spirit::qi::sequence<boost::fusion::cons<boost::spirit:: qi::literal_char<boost::spirit::char_encoding::standard, tr ue, false>, boost::fusion::cons<boost::spirit::qi::negated_ char_parser<boost::spirit::qi::reference<const boost::spiri t::qi::rule<const char*> > >, boost::fusion::nil> > >, boos t::fusion::cons<boost::spirit::qi::sequence<boost::fusion:: cons<boost::spirit::qi::literal_char<boost::spirit::char_en coding::standard, true, false>, boost::fusion::cons<boost:: spirit::qi::reference<const boost::spirit::qi::rule<const c har*> >, boost::fusion::nil> > >, boost::fusion::nil> >, bo ost::spirit::traits::build_attribute_sequence<boost::fusion ::cons<boost::spirit::qi::sequence<boost::fusion::cons<boos t::spirit::qi::literal_char<boost::spirit::char_encoding::s tandard, true, false>, boost::fusion::cons<boost::spirit::q i::negated_char_parser<boost::spirit::qi::reference<const b oost::spirit::qi::rule<const char*> > >, boost::fusion::nil > > >, boost::fusion::cons<boost::spirit::qi::sequence<boos t::fusion::cons<boost::spirit::qi::literal_char<boost::spir it::char_encoding::standard, true, false>, boost::fusion::c ons<boost::spirit::qi::reference<const boost::spirit::qi::r ule<const char*> >, boost::fusion::nil> > >, boost::fusion: :nil> >, boost::spirit::context<boost::fusion::cons<boost:: fusion::unused_type&, boost::fusion::nil>, boost::fusion::v ector0<> >, boost::mpl::identity, const char*>::element_att ribute, boost::fusion::void_> >’

The mistake is that the definition of the term rule is invalid, but it’s not easy to infer that from this mountain of spew.

The problem of error detection and reporting is particularly acute in EDSLs. A domain-specific language will typically have domain-specific errors about which the C++ compiler is ignorant. Any errors the compiler is allowed to emit are likely to be too low-level to make any sense to the EDSL user (like that horrific Spirit error). The library needs to detect and report domain-specific errors. Fortunately, if you are using Boost.Proto, you have some powerful tools at your disposal.6 Let’s see in detail what my advice means for the EDSL I developed in the previous article.

Mad Libs7 Formatting, Revisited

The Mad Libs-like string formatting API from the last article lets users format strings and specify map-like relationships inline. A typical usage looks like this:

std::cout << format("The home directory of {user} is {home}\n"
                  , map("user", "eric")
                       ("home", "/home/eric") );

This expression should print:

The home directory of eric is /home/eric

The EDSL part is the second argument to format. Since we’ll be referring back to it a lot, I’ll duplicate the complete example from the last article:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <map>
#include <string>
#include <iostream>
#include <boost/proto/proto.hpp>
#include <boost/xpressive/xpressive.hpp>
#include <boost/xpressive/regex_actions.hpp>
 
struct map_ {};
boost::proto::terminal<map_>::type map = {};
 
typedef std::map<std::string, std::string> string_map;
 
// Recursive function used to fill the map
template< class Expr >
void fill_map( Expr const & expr, string_map & subs )
{
    using boost::proto::value;      // read a value from a terminal
    using boost::proto::child_c;    // get the Nth child of a non-terminal
    subs[ value(child_c<1>( expr )) ] = value(child_c<2>(expr));
    fill_map( child_c<0>(expr), subs );
}
 
// The 'map' terminal ends the recursion
void fill_map( boost::proto::terminal<map_>::type const &, string_map & )
{}
 
// The old format API that accepts a map of string substitutions
std::string format( std::string fmt, string_map & subs )
{
    namespace xp = boost::xpressive;
    using namespace xp;
    sregex const rx = '{' >> (s1= +_w) >> '}';        // like "{(\\w+)}"
    return regex_replace(fmt, rx, xp::ref(subs)[s1]);
}
 
// The new format API that forwards to the old one
template< class Expr >
std::string format( std::string fmt, Expr const & expr )
{
    string_map subs;
    fill_map( expr, subs );
    return format( fmt, subs );
}
 
int main()
{
    std::cout << format("The home directory of {user} is {home}\n"
                      , map("user", "eric")
                           ("home", "/home/eric") );
}

Fill_map expects to be given expression trees of a certain form. But notice how the second format overload takes the map expression and simply forwards it to fill_map on line 41 without any parameter validation at all. Let’s mess with the expression tree and see what happens:

std::cout << format("The home directory of {user} is {home}\n"
                  , map("user", L"eric")
                       ("home", "/home/eric") );

Notice that I changed one string literal from narrow to wide. When I recompile the code with this most recent change, I get a 50+ line error message8:

Click here to view the error message.

The error occurs deep within our EDSL implementation. Had we validated the expr parameter before calling fill_map, we could have done much better. Let’s see how.

Proto Grammars

At first blush, validating the expr parameter looks difficult. After all, the user could pass one of an infinite number of map expressions of arbitrary depth. But when we put our language-design goggles on, this problem looks much simpler: we just need to find the grammar to which all map expressions must conform. Then we just check that the expression matches the grammar.

This expression:

map("user", "eric")
   ("home", "/home/eric")

…builds a Proto expression tree that looks like this:

Figure 1: A map expression tree

In plain English, we can describe the structure of map expression trees as follows: a map expression is either:

  • A map terminal, or
  • A ternary function call where:
    • The 0th child is a valid map expression tree (note recursion),
    • The 1st child is a string, and
    • The 2nd child is also a string

Using Proto’s support for defining grammars, we can define the MapGrammar as follows (to be explained below):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Define the grammar of map expressions
struct MapGrammar
  : proto::or_<
        // A map expression is a map terminal, or
        proto::terminal<map_>
        // ... a ternary function non-terminal where the child nodes are
      , proto::function<
            // ... a map expression,
            MapGrammar
            // ... a narrow string terminal, and
          , proto::terminal<char const *>
            // ... another narrow string terminal.
          , proto::terminal<char const *>
        >
    >
{};

Let’s take this one piece at a time:

  • Line 2: Proto grammars are simple user-defined structs.
  • Line 3: Inheritance is used to say that MapGrammar is expressed in terms of proto::or_. Proto::or_ is used for grammar alternation like the | operator in EBNF. An expression is allowed to match this or that. In Proto, alternate grammars are tried in order.
  • Line 5: A map expression can be a simple map terminal. Note that proto::terminal<map_> was used to define the global map object on line 9 of the complete example. It is also used here as a grammar that matches that terminal.
  • Line 7: Proto::function defines a grammar that matches Proto expression nodes created by overloaded function-call operators. Proto provides templates like function for all the operators that Proto overloads. A complete list can be found in Proto’s documentation.
  • Line 9: The zeroth child of the function node must match MapGrammar. This is interesting! It looks like we’re recursively defining MapGrammar in terms of itself. Surprisingly, this is legal. In fact, you may already be familiar with this technique. It’s called the Curiously Recurring Template Pattern, or CRTP. It gives Proto a natural way to define recursive grammars.
  • Lines 11-16: Nothing too surprising here. The other two children must be narrow string terminals. The MapGrammar struct itself is empty. That is always the case for Proto grammars.

Pause To Consider

By now, you might be feeling a bit overwhelmed. We just covered a lot of new ground, and this coding style might feel strange to you. But consider for a moment what we’ve just expressed and how concisely we’ve expressed it: we’ve defined in code the grammar for valid map expressions, and it took only a pittance of code to do it. This is quite an accomplishment. Take a moment to become comfortable with the definition of MapGrammar. Grammars are the central pillar of Proto, and once you get grammars under your belt (is that a pillar under your belt? har har), you’ll really be able to do some neat things. In fact, all the powerful and interesting things you can do with Proto begin right here with grammars.

Validating Expressions Against Grammars

No doubt you’re wondering what we actually do with MapGrammar. Proto provides a trait called proto::matches for determining at compile time whether an expression type matches a given grammar. We can use proto::matches in conjuction with C++0x’s static_assert or various C++03 approximations of it (see note below) to halt compilation as soon as an invalid expression is detected.

With static_assert, proto::matches and MapGrammar, we can modify our format overload to validate the expr parameter before passing it to the fill_map function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template< class Expr >
std::string format( std::string fmt, Expr const & expr )
{
    /* READ THIS IF YOUR COMPILE BREAKS ON THE FOLLOWING LINE
     *
     * You have passed to format an invalid map expression.
     * They should be of the form:
     *      map("this", "that")("here", "there")
     */
    static_assert(
        proto::matches<Expr, MapGrammar>::value
      , "The map expression passed to format does not match MapGrammar");
    string_map subs;
    fill_map( expr, subs );
    return format( fmt, subs );
}

When we pass our invalid expression to format now, our error goes from 50+ lines to about 10, including this message9:

c:\scratch.cpp(94): error C2338: The map expression passed
to format does not match MapGrammar

Click here to view the full error.

This error is nicer because:

  • It is shorter!
  • The error message itself indicates what the problem might be.
  • The error happens at the API boundary, not on some random line of code deep in the library’s guts.
  • We’ve helpfully left a comment by the assert to let people know what’s wrong in case the assertion fails, and what to do to fix it.

If you don’t have a C++0x compiler with static_assert, I recommend using Boost.MPL‘s BOOST_MPL_ASSERT_MSG macro, which accepts a compile-time Boolean and a message to display if the Boolean is false. The static assertion on line 9 above would instead look like this:

BOOST_MPL_ASSERT_MSG(
    (proto::matches<Expr, MapGrammar>::value),
    THE_MAP_EXPRESSION_PASSED_TO_FORMAT_DOES_NOT_MATCH_MAPGRAMMAR,
    (MapGrammar));

When this assertion fails, it emits an error like:

c:\scratch.cpp(115): error C2664: 'boost::mpl::assertion_fa
iled' : cannot convert parameter 1 from 'boost::mpl::failed
************(__thiscall format::THE_MAP_EXPRESSION_PASSED_T
O_FORMAT_DOES_NOT_MATCH_MAPGRAMMAR::* ***********)(MapGramm
ar)' to 'boost::mpl::assert::type'

Avoid Follow-on Errors

If you try the above example on gcc-4.5, you’ll find that rather than a shorter error, the static_assert gives a longer one!

Click here to see the full error.

What’s going on here? If you trawl through the error spew, you can see the nice message from the static assertion, but it’s buried in a lot of other junk with two other errors from the guts of our EDSL implementation. Let’s look again at the new implementation of format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template< class Expr >
std::string format( std::string fmt, Expr const & expr )
{
    /* READ THIS IF YOUR COMPILE BREAKS ON THE FOLLOWING LINE
     *
     * You have passed to format an invalid map expression.
     * They should be of the form:
     *      map("this", "that")("here", "there")
     */
    static_assert(
        proto::matches<Expr, MapGrammar>::value
      , "The map expression passed to format does not match MapGrammar");
    string_map subs;
    fill_map( expr, subs );
    return format( fmt, subs );
}

The static_assert on line 10 causes the nice diagnostic, but gcc helpfully keeps right on compiling, eventually reaching the call to fill_map on line 14. We’ve already established that the call will fail to compile, but nobody told gcc it was OK to stop!

In general, it’s not enough to issue a diagnostic for the known errors. We must also avoid the follow-on diagnostics from overeager compilers like gcc. The answer is usually quite simple: move the guts to a separate function, and use static dispatch to call that function or an empty one depending on whether validation succeeded. A little code should make it clear:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template< class Expr >
std::string format_impl( std::string fmt, Expr const & expr, boost::mpl::true_ )
{
    string_map subs;
    fill_map( expr, subs );
    return format( fmt, subs );
}
 
template< class Expr >
std::string format_impl( std::string fmt, Expr const & expr, boost::mpl::false_ )
{
    return std::string(); // never called for valid imput
}
 
template< class Expr >
std::string format( std::string fmt, Expr const & expr )
{
    /* READ THIS IF YOUR COMPILE BREAKS ON THE FOLLOWING LINE
     *
     * You have passed to format an invalid map expression.
     * They should be of the form:
     *      map("this", "that")("here", "there")
     */
    static_assert(
        proto::matches<Expr, MapGrammar>::value
      , "The map expression passed to format does not match MapGrammar");
 
    /* Dispatch to the real implementation or a stub depending on
       whether our parameters are valid or not.
     */
    return format_impl( fmt, expr, proto::matches<Expr, MapGrammar>() );
}

We added two overloads of a new function format_impl. The first takes an extra argument of type boost::mpl::true_ and does the real work. The second takes boost::mpl::false_ and simply returns an empty string. The original format function is now just a shell that (maybe) issues a diagnostic and dispatches to one or the other overload. Proto::matches conveniently inherits from mpl::true_ or mpl::false_ accordingly to make this possible. With this change, the full error is much shorter:

scratch.cpp: In function ‘std::string format(std::string, c onst Expr&) [with Expr = boost::proto::exprns_::expr<boost: :proto::tag::function, boost::proto::argsns_::list3<const b oost::proto::exprns_::expr<boost::proto::tag::function, boo st::proto::argsns_::list3<boost::proto::exprns_::expr<boost ::proto::tag::terminal, boost::proto::argsns_::term<map_>, 0l>&, boost::proto::exprns_::expr<boost::proto::tag::termin al, boost::proto::argsns_::term<const char (&)[5]>, 0l>, bo ost::proto::exprns_::expr<boost::proto::tag::terminal, boos t::proto::argsns_::term<const wchar_t (&)[5]>, 0l> >, 3l>&, boost::proto::exprns_::expr<boost::proto::tag::terminal, bo ost::proto::argsns_::term<const char (&)[5]>, 0l>, boost::p roto::exprns_::expr<boost::proto::tag::terminal, boost::pro to::argsns_::term<const char (&)[11]>, 0l> >, 3l>, std::str ing = std::basic_string<char>]’: scratch.cpp:126:55: instantiated from here scratch.cpp:112:9: error: static assertion failed: "The map expression passed to format does not match MapGrammar"

Conclusions and What's To Come

Thanks for reading. Since I'm priming you guys to be library authors, I feel obligated to give you the tools to make your libraries user-friendly. As you can see, we had to be a bit proactive about making our code behave well when passed garbage, but it wasn't so hard. Although I talked mostly about EDSLs and Proto, these techniques are applicable very broadly:

  1. Validate template parameters at API boundaries.
  2. Use C++0x's static_assert or a C++03 equivalent to issue readable diagnostics.
  3. Leave detailed comments by the static assertions to let people know what has gone wrong and how to fix it.
  4. Dispatch to stubs on invalid input to avoid follow-on failures.

These techniques can greatly reduce the amount of compiler spew C++ programmers encounter on a daily basis.

Proto grammars make validating expression trees easy and (dare I say it?) fun. But they are far more useful than that. You can use Proto grammars to restrict Proto's operator overloads to only those that create valid trees. And by embedding semantic actions (a.k.a transforms) within Proto grammars, you can write algorithms that manipulate trees and generate code in powerful ways. In future articles, we'll dig deep into Proto grammars and transforms. But first, we'll take a closer look at Proto expressions and how to extend them, adding and customizing member functions, making them anything but dumb, static trees.

Until next time, don't forget to validate your parameters. And if you see any bad template errors, file a bug!


  1. Previous articles in this series referred to them as domain-specific embedded languages, or DSELs. Due to feedback I've received, I've decided to switch to the more commonly-used term EDSL. I'm still referring the same thing. 

  2. A knowing reference to Neal Stephenson's fictional Metaverse in his book "Snow Crash", one of my personal faves. 

  3. Boost.StaticAssert and Boost.Concept_check, for example. 

  4. Compiled with g++ 4.5.0 against Boost trunk as of Sept 16, 2010. 

  5. I'm sorry to pick on the Spirit authors here, but I had to pick on somebody. Spirit is actually better than most at catching and reporting invalid input, and finding this particular chink in Spirit's armor took some fiddling. 

  6. I'm not claiming Boost.Proto is a silver bullet. It provides the tools. You have to use them. Boost.Spirit is actually implemented with Proto, but that didn't help in this example. It appears that Spirit is letting this invalid input through and failing badly in deeply nested template instantiations. 

  7. Mad Libs is a registered trademark of Penguin Group (USA) Inc. 

  8. Tested with Microsoft Visual C++ 2010 

  9. Tested with Microsoft Visual C++ 2010 

It’s the tilde before factor on line 9.Powered by Hackadelic Sliding Notes 1.6.5
Posted Thursday, September 23rd, 2010 under Boost.

28 Responses to “Expressive C++: Why Template Errors Suck and What You Can Do About It”

  1. Robert Ramey says:

    In your example, is there a reason you chose not to use the boost concept library? In other words, where does the boost concept library fit (or not fit) in here?

    template< class Expr >
    std::string format( std::string fmt, Expr const & expr )
    {
        /* READ THIS IF YOUR COMPILE BREAKS ON THE FOLLOWING LINE
         *
         * You have passed to format an invalid map expression.
         * They should be of the form:
         *      map("this", "that")("here", "there")
         */
        static_assert(
            proto::matches<Expr, MapGrammar>::value
          , "The map expression passed to format does not match MapGrammar");
        string_map subs;
        fill_map( expr, subs );
        return format( fmt, subs );
    }

    Robert Ramey

      Quote
    • Eric Niebler says:

      That’s a good question, and one that was raised on the Boost developers list when I first published this article. A debate ensued about whether it was possible to concept-ify Proto expressions and grammars. I believe the result of that discussion was that, yes, concepts (the C++0x variety) were powerful enough for that.

      As to whether or not it’s possible to use Boost.Concept_check for this … I don’t known.

        Quote
  2. alfC says:

    Nice article. You mention that gcc is overeager because it continues after failing the static_assert. Isn’t the flag “-Wfatal-errors” supposed to solve this problem?

      Quote
    • Eric Niebler says:

      You can use -Wfatal-errors on gcc to improve the error. But when you’re distributing library code, it shouldn’t come with a list of switches that make it work on various compilers. Your end user is the one who picks the compiler switches, and they may not want to compile with -Wfatal-errors for other reasons.

      Library code should make as few assumptions as possible about its environment and simply do the best it can with the tools available: standard C++.

        Quote
      • alfC says:

        Good point. Besides, although I use -Wfatal-errors almost all the time, sometimes I have to switch it off just to know a little bit more about the error. On the other hand, I hate to see the code such much ‘ruined’ aesthetically with those auxiliary *_impl functions, it is just not pretty. Thank you for the great post.

          Quote
      • Vasilis Vasaitis says:
        Your end user is the one who picks the compiler switches, and they may not want to compile with -Wfatal-errors for other reasons.

        Or the end user might simply not know about -Wfatal-errors. :^/ Thanks guys! I had already trained myself a long time ago to ignore all of g++’s errors after the first one, but this is better. :^)

          Quote
  3. Ian says:

    Am I the only one who finds

    template< class Expr >
    std::string format_impl( std::string fmt, Expr const & expr, boost::mpl::false_ )
    {
        return std::string(); // never called for valid imput
    }

    a little bit ugly? As one of the things I expect from a type system is to relieve me from writing things like assert (false) or, even more so, // this should never happen

    IMHO enable_if is your friend:

    template <typename Expr>
    typename std::enable_if<proto::matches<Expr, MapGrammar>::value, std::string>::type
    format (std::string fmt, const Expr & expr)
    {
        // ...
    }
     
    // and for a nice error message
    template <typename Expr>
    typename std::enable_if<!proto::matches<Expr, MapGrammar>::value>::type
    format (std::string fmt, const Expr & expr)
    {
        static_assert (sizeof (Expr) < 0,      // or some other "false" that depends on Expr
            "Some descriptive error message");
    }
      Quote
    • Eric Niebler says:

      Agree. The only reason I didn’t do it that way is because I didn’t want to explain SFINAE and enable_if in this article. Static dispatch seems less magical than SFINAE.

        Quote
      • Eric Niebler says:

        Actually, let me amend that. You have two overloads of format: one that returns a std::string and one that returns void. It is likely to be used like this:

        std::string s = format("...", map("this", "that")("here", L"there");

        Now imagine that there is an error in the map expression (as there is above). The overload that returns void will be selected and the static assertion will fire; well and good. But gcc will continue compiling and then complain that you can’t assign void to a std::string. Not good.

        No, my original formulation was just fine, and I’ll stick with it.

          Quote
  4. Marsh Ray says:

    Nice article. I have to admit that when I saw the definition of ‘struct MapGrammar’, my first thought was “man it’s gonna suck to interpret the template errors from that thing”.

    I wish templates did in fact do more parameter validation with static_asserts and concept-like checking.

    I see much agreement that there is “too much useless information” in templates. But the reason that information is there is because the compiler couldn’t tell if it was useless or not. Only after you’ve found and corrected the problem do you know if it was useless. So in ambiguous situations, I’d prefer to error on the side of too much information rather than not enough.

    How does the compiler know whether to report a typedef name or expand its definition? Couldn’t it eliminate some namespace qualifiers? Couldn’t we just have ‘string’ instead of ‘std::basic_string<blah blah allocator blah blah..’? It might be nice to be able to tag typedefs as a hint to the compiler that their full expansion is probably less interesting than their name.

      Quote
  5. This article does great with the “what to do about it” part, but IMO it doesn’t really address the “why template errors suck” part. Saying that template errors suck because people aren’t doing parameter validation is kinda like explaining heart attacks by saying people don’t eat their vegetables. It tells me what I shoulda done different, but it doesn’t explain why not following the prescription leads to bad health.

      Quote
    • Eric Niebler says:

      Guilty as charged. I think I tried to do too much in this article. It could really be 3:

      1. Where do bad template errors come from?
      2. What are some general techniques to avoid them?
      3. How does that advice relate to EDSLs built with Proto?

      As it is, it’s overly long, and I totally skipped (1). <sigh>

      I know you know how lack of parameter validation at API boundaries leads to bad template errors, but for everybody else, my hand-wavy explanation would go something like this: the bulk of a template error is its so-called instantiation backtrace, which is the compile-time equivalent of a runtime stack trace that you see in a debugger. An instantiation backtrace is the sequence of template-instantiations-that-caused-instantiations that ultimately led to the failure. If we had known up-front that a requirement wasn’t met, we could bomb out early and avoid the huge backtrace.

      And it’s not just the backtrace we’re concerned with. When errors are allowed to happen deep in the guts of a library, they typically involve library details about which the library user knows nothing. In contrast, when an error can be reported up front, the error will involve types that the users themselves have specified. It’s way easier for them to understand how they might have caused the error and what they need to do to fix it.

        Quote
      • Right. And now that you’ve said all that, it becomes much easier to understand what’s wrong with GCC’s template error reporting: it’s a combination of two things:

        1. instead of starting the backtrace with the innermost frame, where the error actually occurred, it starts with the outermost, where there’s very little illuminating information. Yes, it may point to the location of the mistake in the users code but the user hardly gets any information about the nature of the error.
        2. GCC (by default) continues to report errors after the first one.

        As a result, the most useful information is neither at the beginning nor at the end of this long backtrace, but buried somewhere in the middle. You can pass --Wfatal-errors to GCC to get it to stop after one error, or you can use STLFilt to reorder the error messages, but those fixes are not under control of the template library author, because users get to choose how compilation is done.

          Quote
  6. Ahmed Charles says:

    This might not be the best place to ask, but are there plans to use C++0x std::true_type/false_type instead of the mpl versions of the same. Or even incorporating new C++0x library functionality into various libraries to eliminate cases like this?

      Quote
    • Eric Niebler says:

      I’m not sure what the implications would be of, say, typedef’ing mpl::true_ to std::true_type, or even what the benefits are. But yes, there have been efforts here and there to integrate C++0x features into various libraries. I think boost::result_of uses decltype now (you might need to flip a switch to get that behavior, I can’t recall).

        Quote
  7. djowel says:

    “Not on your blog, not to Reddit, but to the author of the offending library.”

    Hehe, so the proper forum to complain to is Spirit’s mailing list :-) … I’m wondering why proto didn’t catch that though.

      Quote
    • Eric Niebler says:

      Sorry Joel, I couldn’t report the bug and then have you fix it before the article came out. No hard feelings? :-)

      Proto will only catch the things you tell it about. I’d check Spirit’s grammar and make sure that the complement operator is not allowed to apply to rules.

      EDIT: Not to set a bad precedent, I filed the bug here.

        Quote
  8. Thomas Heller says:

    First, let me say that i use clang exclusively for my development, BUT:

    You claim that it brings us nicer error messages, while this is true (most of the time), the messages spilled out by clang are way more verbose than those of gcc.

    The verbosity isn’t a bad thing, it brings you exactly to where the error happened and why, though it might scare the shit out of your users encountering theses messages :)

    One of the most recent error messages i got during the last days was that my result type deduction calculations where wrong (using std (boost) result_of protocol). I currently can not see how to apply the techniques you discussed in the article to this specific problem. IMHO, what we could use most is a sensible template debugging facility (compiler diagnostics don’t help here i am afraid, cause you can not really trace where, why and with what arguments this particular template failed, or behaved in a way that is causing the error).

    One of my closest friends during debugging heavy template code is:

    std::cout << typeid(T).name << “\n”;

    compile -> run -> pipe the output through c++filt -t

      Quote
    • Eric Niebler says:
      One of the most recent error messages i got during the last days was that my result type deduction calculations where wrong (using std (boost) result_of protocol). I currently can not see how to apply the techniques you discussed in the article to this specific problem.

      Thomas, these techniques apply when you have a working template library and you want to improve the error messages users see when they use it incorrectly. They don’t help when your library has bugs in it. The errors that you as library author see when writing your own library are yours to cherish.

        Quote
      • Thomas Heller says:
        The errors that you as library author see when writing your own library are yours to cherish.

        Very true. However, many libraries take Polymorphic Function Objects (PFO) to let the user decide how to transform/fold/do work/whatever. These libraries use (ignoring decltype and friends for now) the result_of protocol. This means, for user provided PFOs, a nested result template often is the main interface between the library and the user’s code. And the error message the compiler gives you wrt. to these buggy result types (in the users code), start from deep within your library propagating through the internals of boost::result_of.

          Quote
        • Eric Niebler says:

          Right, if the user gives you a template to instantiate, you have no choice but to instantiate it, and that might cause errors. But you can choose when and where to do the instantiation. Can you calculate the types with which you’ll instantiate it at the API boundary? Then you can do the instantiation there and fail early.

          Even then, the guts of boost::result_of will show up in the backtrace. There’s not a whole lot you can do about that, though.

            Quote
          • I think result_of is a particularly thorny example in C++03—because of the limitations of SFINAE, it’s not possible to test whether the protocol is properly supported without causing an error when it isn’t. In C++0x, with extended SFINAE, I don’t expect we’ll have that problem.

              Quote
  9. Sohail says:

    Awesome post Eric. This kind of coding can really make life easier for everyone.

    With proto, could you generate the error:

    “Cannot map type T1 to type T2 on line 10″?

    Thanks.

      Quote
    • Eric Niebler says:

      Thanks, Sohail. I’m not exactly sure what you’re asking, though. Can you elaborate a bit on what you’d like to do?

        Quote
      • Sohail says:

        One of the errors you were simplifying was that you were passing in a wchar* where a char* was expected and to avoid the error, you checked if the grammar matched. In this case, the error message is “Grammar did not match.”

        Would it be possible to know why it doesn’t match and generate an appropriate error? In your example, you might want to generate the error: “The second argument must be of char* but you gave me a wchar* on line N”

          Quote
        • Eric Niebler says:

          Alas, no. I have kicked around various ideas for embedding parse failure messages within Proto grammars, but it hasn’t gone much further than that. And you certainly couldn’t get line number information from the user’s expression. That information is simply not available to Proto.

            Quote
          • Sohail says:

            Yeah, I figured as much.

            Great post. Loved the part about validating parameters. It’s such an obvious practice but so hard to do and so important when you need to debug!

              Quote

Leave a Comment (post replies using links below individual comments)