Using String Literals as C++ Template Parameters

This post explores how to trick C++ into accepting a string literal as a template parameter.

Templates can take literal values as parameters as well as type names. These literal values are also known as non-type template parameters (NTTP). The rules about what types of literals templates can accept is somewhat complicated but basically prior to C++20 integral types are supported whereas floating point is not. Floating point values as NTTP was added in C++20. Arbitrary objects are not allowed but objects that meet a restricted set of rules such that their classes are effectively literal constants are allowed. Such classes are called "structural types". The addition of literal classes as NTTP was introduced in C++20. (See Literal Classes as NTTP).

A literal class type has the following properties:

  • all base classes and non-static data members are public and non-mutable
  • the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.

So because double quoted string literals are not allowed as a NTTP, we must wrap it in a structural type that we will call StringLiteral.

TemplateUtils.h
#ifndef TEMPLATE_UTILS_H
#define TEMPLATE_UTILS_H

#include <algorithm>

namespace TemplateUtils {
// Literal class type that wraps a constant expression string.
// Uses implicit conversion to allow templates to seemingly
// accept string literals (in double quotes).
// See https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/
    template<size_t N>
    struct StringLiteral {
        constexpr StringLiteral(const char (&str)[N]) {
            std::copy_n(str, N, value);
        }
        char value[N];
    };

};

#endif //TEMPLATE_UTILS_H

Our wrapper class is a template parameterized on the size of the string that it will hold. This class is actually declared using the struct keyword so that all data members are automatically public. Our only data member is an array of characters to hold the string literal. Array of characters is considered a structural type. The constructor is declared constexpr which is key to making a literal type. The constructor takes a reference to a fixed array of characters so that we can pass double quoted string literals to the constructor.

Taken all together this StringLiteral template class can be used to create literal objects that can simply be compared member by member, i.e. a structural type. This is key because only one object will be created by the compiler for a given double quoted string, no matter how many places we use it in the code. The compiler accomplishes this by comparing, member-by-member to other StringLiterals it has created, i.e. structural typing.

main.cpp
#include <iostream>
#include "TemplateUtils.h"

using namespace std;

template <TemplateUtils::StringLiteral STR>
char const *literalFunc() {
    return STR.value;
}

int main(int argc, const char * argv[]) {
    const char * p1 = literalFunc<"Hello">();
    const char * p2 = literalFunc<"Aloha">();
    const char * p3 = literalFunc<"Hello">();
    TemplateUtils::StringLiteral sl1("Hello");
    TemplateUtils::StringLiteral sl2("Hello");
    cout << (void*)p1 << " " << p1 << endl;
    cout << (void*)p2 << " " << p2 << endl;
    cout << (void*)p3 << " " << p3 << endl;
    cout << (void*)sl1.value << " " << sl1.value << endl;
    cout << (void*)sl2.value << " " << sl2.value << endl;
    return 0;
}

Notice that the test code uses StringLiteral twice when calling literalFunc() with double quoted literal "Hello". Because NTTP are storage class static, the compiler was able to store just a single copy (which is why p1 and p3 are the same address). When creating local copies of StringLiterals, however, multiple independent copies are created on the stack (automatic storage class).

0x102c5e3c8 Hello
0x102c5e3d0 Aloha
0x102c5e3c8 Hello
0x7ffeecfe3500 Hello
0x7ffeecfe34f8 Hello