Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Ironing Templates' Syntactic Wrinkles

Last updated Jan 1, 2003.

The template syntactic rules were carved in stone in 1997, when the technical work on the final draft of the C++ standard was completed. However, in the past eight years, we have gained more experience and savviness with templates -- as have our compilers.

This real world experience has encouraged programmers and compiler writers to propose core language extensions that would make template programming a tad more intuitive by removing a few rigorous restrictions or by allowing compilers to take charge of things. Here I present some of these proposals.

The Angle Bracket Blues

C++ programmers who aren't new to templates have learned the hard way why their compiler moans if it sees a declaration such as this:

vector<vector<char>> lines; //compilation error because of >>

My faithful (albeit a bit behind) C++ BuilderX issues this cryptic error message:

"main.cpp": E2451 Undefined symbol 'lines' at line 4".

This isn't exactly an elucidating error message, but I learned what my compiler is really trying to say: the sequence >> can't be parsed as two template delimiters. Because of the "maximal munch" rule, it's parsed as the right shift operator, which of course makes no sense at all in this declaration. Comeau gives a much clearer error message:

line 4: error: space required between adjacent ">" delimiters of
nested template argument lists (">>" is the right shift operator)

It's nice that compilers tell you exactly what's wrong. However, if a compiler already knows what the programmer meant, why not "do the right thing" and treat the sequence >> as two closing angle brackets? Such a proposal has been made several times. In fact, there are compilers that already silently treat the >> sequence as two closing angle brackets in this context, often issuing a warning message.

As tempting as it is, this proposal raises two problems. First, it violates the "maximal munch" rule. Although this rule isn't sanctified, "special cases" are best avoided in general. There is a however a more serious problem. In certain (rare) occasions, the >> sequence might be truly ambiguous:

template <int N> class Cont;
template <class T> void funky() {}
template <int I> void funky() {}
int main()
{
 funky<Cont<8>>4> > (); // << here is the right shift op
}

However, such contrived examples are pretty rare. Besides, the compiler has a few hints: In the first example, the >> token appears after a type-name. As such, it cannot be a valid right shift operator anyway.

The typename Keyword

The typename keyword disambiguates qualified names. By default, in a qualified name of the form

C::mem; //mem is treated as a non-type identifier

mem is assumed to be an object or a variable of class C. When programmers wish to override this interpretation, they precede the typename keyword, indicating that mem is a type, not an object. So far, so good. Yet there are contexts in which typename is not allowed, to the surprise of most programmers. In the following example, all of the occurrences of typename are valid, except for the last one:

template <typename T> class Vec{ //ok
public:
 typedef T ELEM;
};
template <typename T> //ok
 void func( typename Vec<T>::ELEM & e) //ok 
template <> 
 void func( typename Vec<T>::ELEM & e) //error

This restriction seems too rigorous. The last typename is indeed redundant: the compiler must see this specialization after seeing the primary func() template, so it already knows that Vec<T>::ELEM must be a type-name. However, compilers can simply ignore the redundant typename in this case and accept the code as is. Ignoring typename wouldn't be a precedent. You'd be amazed of how many keywords compilers nowadays ignore auto, register, and, in many cases, inline are the first candidates. volatile, restrict<> and long (when used in a declaration of long double variables, in environments that don't support this type) are other examples. Adding another keyword to this list shouldn't cause any harm.

Using Quoted Literals as Template Arguments

It's illegal to use a quoted literal as a template argument:

template <const char * txt>
class Logger
{
//..
void Write();
};
Logger<"Test"> lg().Write(); //error

The same restriction applies to floating point literals. Prohibiting quoted literals isn't an arbitrary whim as it might first appear. The problem is this: the "Test" literal is of type const char [5]. However, when it's used as a template argument its type decays to const char *. Can you see what the problem is? If we were to create multiple occurrences of the Logger template with the same quoted literal, there is no guarantee that these occurrences would refer to the same instance because each quoted literal might have a different memory address:

int main()
{
Logger<"Test"> lg().Write(); 
}
void func()
{
 Logger<"Test"> lg().Write(); //a separate instance or not?
}

Still, programmers find this restriction too rigorous. With a few compiler switches, it is possible to avert the instance multiplication problem. Furthermore, some compilers that accept this code as a nonstandard extension store the literals in the internal representation of the template.

Summary

It's too early to tell which of these proposed extensions will make it into C++0x, if any. However, past experience shows that when a critical mass of compilers enables a certain feature as a nonstandard extension, that feature is eventually incorporated into ISO C++. We've witnessed this phenomenon with hashed containers, regular expressions and C99' inline.