Card 1 / 7
A template parameter does not have to be a type. N here is a value — a std::size_t fixed at compile time — and it becomes part of the type, so FixedBuffer<int, 4> and FixedBuffer<int, 8> are two unrelated types with no runtime size field between them.
| expression | value |
|---|---|
FixedBuffer<int, 4>::size() |
4, a compile-time constant |
FixedBuffer<int, 8>::size() |
8 |
buffer[0] on a fresh FixedBuffer<int, 3> |
0 — the array is value-initialised |
buffer[0] after buffer[0] = 7 |
7 |
The parameter list mixes both kinds: template <class T, std::size_t N>. Inside, T values[N]{}; declares the storage — the trailing {} value-initialises every element, so a fresh buffer reads as zeros. size() is static constexpr and just returns N, which is why it can be used in a static_assert. operator[] needs a const overload as well as the mutable one, so const buffers stay readable.
// Write a class template with TWO kinds of parameter: a type parameter T and
// a NON-TYPE parameter N of type std::size_t, giving the buffer its length.
//
// It holds a C array of N elements called values, value-initialised, and
// offers:
// static constexpr std::size_t size() -> N
// T& operator[](std::size_t index)
// const T& operator[](std::size_t index) const
template <class T, std::size_t N>
struct FixedBuffer {
T values[N]{};
static constexpr std::size_t size() { return N; }
T& operator[](std::size_t index) { return values[index]; }
const T& operator[](std::size_t index) const { return values[index]; }
};Card 2 / 7
A parameter pack holds zero or more arguments behind one name. Two operators work on it: sizeof...(Args) gives the count as a compile-time constant, and args... expands the pack wherever a comma-separated list is expected.