If I had to pick out my favourite features planned for C++20, then this one would definitely be amongst the top 5 since I love compile time programming. This feature makes it more natural to write templated code since it allows you to group data together and pass it to a template without having to resort to hacks.

To explain what this feature is about, I will start by talking about normal non-type template parameters from pre-C++20.

Non-Type Template Parameters

A non-type template parameter might sound a bit like gibberish the first time you hear it, but when you think about it the phrase makes sense. Consider the following snippet:

//some kind of std::array knock-off
template <typename StoredType, size_t size>
struct Array
{
    StoredType data[size];
};

void usage()
{
    Array<int, 100> oneHundredInts;
}

This should look familiar to std::array. Array is a template that has two parameters: StoredType and size. The former is a type template parameter since it expects a type - just like how i in void f(int i); is an int parameter because it expects an int. Since the second parameter of our template takes a value of size_t and not a type, it is a non-type template parameter. In other words, a non-type template parameter can be seen as a parameter that takes a value instead of a type.

Even though non-type template parameters have been around since before C++20, the kinds of types that can be used with them has been very restricted. These basically only included integral types, enum types, pointer types and reference types. How boring!

C++20 Gives Us More

In C++20, the restrictions mentioned above are relaxed to the point where we can pass any user-defined types, as long as they fulfill certain requirements. This means we can in principle do the following:

struct TwoInts
{
    int i;
    int j;
    //...
};

template <TwoInts ints>
int sum()
{
    return ints.i + ints.j;
}

void usage()
{
    int result = sum<TwoInts{1, 2}>();
}

This is very nice!

Requirements

There are two main requirements to consider to enable this capability for your types. The first one is pretty obvious - your type needs to be a literal type. This basically means that it can be used in a constexpr context. This makes sense since templates are always evaluated at compile time, so if your type cannot even be constructed at compile time, how could you ever use it as a template parameter?

The second requirement is a bit more subtle. Since a template instantiates code based on the exact arguments given to the parameters, you definitely want the same set of arguments to give the same result every time. In other words, similarly to how f<1, 5>() should result in the same instantiation every time, f<twoInts>() should also result in the same instantiation whenever the content of twoInts is identical. But what does “identical” mean here?

An obvious way is to use operator== as a way to identify that separate inputs to a template are the same (for example if inputA == inputB then these two use the same instantiation: f<inputA>(); f<inputB>();). This is problematic though when the user can implement their own operator==, as it might not take every member into account, or may even return false all the time.

As currently proposed, this is solved by requiring your type to have strong structural equality. This means that the type has a defaulted operator<=> (read more about this operator in my other post) that returns std::strong_ordering or std::strong_equality, and additionally all members and bases of this type must also have strong structural equality. If your type follows this, it is guaranteed that any difference between two values is reflected in the equality comparison between them.

As additional requirements, your type also cannot have any mutable or volatile subobjects.

Use Cases

I think this is one of those features that opens the door into a space that needs to be explored. I’m sure we’ll see usages of this feature down the line that we haven’t yet thought of that are going to be very cool. There are however a few things that I am keen on using it for:

Compile Time String Parsing

This one is probably the most cited reason for why this feature is wanted, and for good reason.

You can create a constexpr string type static_string or similar and proceed to write string parsing routines that would look a lot more like idiomatic C++. For example, this could be used to pre-compile regex literals or queries, do compile time string hashing, or if you wanna go completely nuts you could parse JSON and output a static nested memory structure with the data, all as part of compilation.

Configuration Structs

I sometimes find myself writing generic data structures that have to make trade-offs to support some generic case which might prove to be an unnecessary cost in a simpler specific case. For example, consider a string type that uses small buffer optimisation (SBO) to improve speed by skipping allocation for small strings. That’s great. But what if we have a case where we want to use this string and we know that our strings are always going to be large? Here we would ideally want to skip SBO entirely since it does incur a small cost due to branching.

We can make this configurable as a template parameter: template <bool useSbo> class OurString{};. This works well, but what if we have two things to configure? Or three? This becomes unwieldy quite quickly.

Instead, let’s create a configuration struct that is passed as a non-type template parameter that has all our settings in one place.

struct OurStringConfig
{
    bool useSbo;
    size_t sboSize;
};

template <OurStringConfig config>
class OurString
{
    //config.useSbo
    //...
};

As you can see, we get a single place for all our settings where we can also easily put default values. This kind of type configuration is great since we can now conditionally select what types we use, and avoid certain procedures to be run using if constexpr. The compiler can also do a better job at optimising since the options are available at compile time.

Even though it was possible to achieve this kind of type configuration before, it is now a lot neater.

Constexpr Function Parameters

Sometimes I have wanted to be able to specify that some of my function parameters are constexpr when I know that they are always going to be available in compile time. This is also something that can be solved with non-type template parameters:

//this is not valid:
void f(constexpr SomeType st, OtherType ot)
{
}

//instead we can do:
template <SomeType st>
void f(OtherType ot)
{
}

//and use it like:
void usage()
{
    f<SomeType{}>(OtherType{});
}

Identifying that some of your arguments are always known in compile time and passing them like this instead is beneficial in the same way as the type configuration mentioned above; you get a way to adapt the code in compile time to save cycles in runtime.

Conclusion

I have a weak spot for compile time programming so I am really stoked for this feature! I am curious to see what interesting use cases other people come up with when using it. Thanks for reading!

More C++20

If you’re curious about other C++20 features, make sure to check out my other posts below.