C++11 Right Angle Bracket Fix for Nested Templates
In early standards of the C++ programming language, declaring nested templates required inserting a whitespace between consecutive closing angle brackets to avoid syntax errors. The C++11 standard resolved this longstanding usability issue by introducing context-sensitive parsing rules that allow the compiler to recognize adjacent right angle brackets as two separate closing template delimiters rather than a single bitwise right-shift operator. This article explains the underlying parsing conflict in pre-C++11 compilers, the mechanics of the C++11 solution, and how the language handles potential ambiguities in template expressions.
The Pre-C++11 Problem: The Maximal Munch Rule
Prior to C++11, compiling nested templates without spaces resulted in a compiler error. For example, the following declaration was invalid under C++98 and C++03:
std::vector<std::vector<int>> matrix; // Compile error in C++98/03Developers were required to manually separate the closing brackets with whitespace:
std::vector<std::vector<int> > matrix; // Valid in C++98/03This limitation stemmed from the standard lexical analysis phase of the compiler, specifically a principle known as "Maximal Munch" (or the longest match rule). Traditional compilers perform lexical analysis (tokenization) independently of syntactic analysis (parsing).
When the lexer encountered two consecutive right angle brackets
(>>), Maximal Munch dictated that the lexer must
consume the longest possible sequence of characters that forms a valid
token. Because >> matched the right-shift operator
token, the lexer emitted >> instead of two separate
> tokens. When the parser subsequently evaluated the
template declaration, it received a right-shift operator where it
expected the closure of the outer template argument list, triggering a
syntax error.
How C++11 Resolved the Issue
Standard proposal N1757 addressed this behavior by introducing a
contextual lexical rule in C++11. Rather than treating tokenization as
strictly independent of the parser, C++11 modified the grammar so that
the meaning of >> depends on the surrounding
syntactic context.
Under C++11 rules, when the compiler parses a template parameter list
or a template argument list, a > character is treated as
a closing bracket delimiter unless it explicitly forms part of an
enclosed expression. If the compiler encounters two consecutive right
angle brackets (>>) while inside an active template
argument list, it splits the token into two distinct closing bracket
delimiters (> and >).
This allows expressions like
std::vector<std::vector<int>> to be correctly
recognized as closing the inner vector<int> and the
outer vector<...> sequentially without requiring
artificial whitespace. The same logic extends to deeper nestings, such
as triple brackets (>>>), parsing them as three
distinct closing delimiters.
Handling Ambiguity in Template Expressions
Allowing >> to be treated as closing brackets
introduced a potential grammar conflict: non-type template arguments can
contain expressions that legitimately use the right-shift operator. For
example:
template <int N>
struct BitShiftContainer {};
// Ambiguity: Is ">>" a right shift or two closing brackets?
BitShiftContainer<16 >> 2> container; To prevent the compiler from prematurely closing the template at the
>> characters, C++11 established a precedence rule:
inside a template argument list, any >> token is
always treated as closing brackets unless the expression using the
right-shift operator is wrapped in parentheses.
To use the bitwise right-shift operator inside a template argument list, the programmer must parenthesize the expression:
BitShiftContainer<(16 >> 2)> container; // Valid in C++11: parentheses protect the operatorThe presence of parentheses informs the parser that the enclosed
tokens belong to an arithmetic expression, thereby restoring the
standard operator interpretation of >>.
Impact on C++ Development
The C++11 right angle bracket fix resolved one of the most common syntactical frustrations for C++ programmers without breaking backward compatibility. By creating a context-aware boundary between the lexer and the parser for template argument lists, the language simplified template-heavy idioms, improved readability, and eliminated an unnecessary vestige of early compiler design.