To understand why we need our StringLiteral class, we first need to look at Non-Type Template Parameters (NTTPs). These aren't new, they've been a fundamental part of C++ since C++98.
You've already used them, probably without thinking much about it. The most common example is std::array.
#include <array>
// T is a TYPE parameter (e.g., int)
// N is a NON-TYPE parameter (e.g., 5)
template<typename T, size_t N>
class std::array;
// We provide a type 'int' and a value '10'
std::array<int, 10> my_array;
For decades, the C++ standard was very strict about what kinds of non-type parameters were allowed in template arguments.. Before C++20, the list was mostly limited to:
- Integral types (like int, size_t, bool).
- Enumeration types.
- Pointers and references to objects or functions with external linkage.
A string literal, like "hello", has internal linkage. This means it's essentially private to its source file (.cpp file). The compiler is free to store it however it wants, and it might even merge identical string literals from different files into a single location. Because a string literal doesn't have a stable, unique address across the entire program (i.e., external linkage), a pointer to it was forbidden as a template argument.
Due to this limitation we couldn't just write MyTemplate<"hello">. The rules of the language forbade it. We were stuck—until C++20 expanded the rules for what kind of types could be used as NTTPs, opening the door for our StringLiteral solution.