Variable Templates

An introduction to variable templates, allowing us to create variables at compile time.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated

Let's introduce our second type of template - variable templates. Let's imagine we were creating a library for our colleagues to use. Our library needs mathematical constants, such as Pi:

constexpr double Pi {
  3.141592653589793238462643383279
}

We elected to use a double here, but that might be presumptuous. We don’t know how our library is going to be used by our consumers, so the preferred type was just a guess. In some scenarios, they might prefer a float, or a custom type.

That conversion could be done at run time, but it would have a performance cost, and may reduce type safety.

We could create different versions of our variable:

constexpr int PiInt {
  3
};

constexpr double PiFloat {
  3.141592f
};

constexpr double PiDouble {
  3.141592653589793
};

But, this has the same disadvantages we saw when trying to create classes to accommodate different data types. Aside from the code duplication and the verbose names, this approach requires us to know all the types our users will want in advance.

Creating Variable Templates

Instead, we could create a variable template, using a template parameter in place of the type. In this example, our parameter will be a typename and we’ll call it T:

template <typename T>
constexpr T Pi {
  3.141592653589793238462643383279
};

Now, we can ask our variable template to create new variables, using the < and > syntax. Given that this template requires a type as its parameter, would provide that type as an argument:

template <typename T>
constexpr T Pi{3.141592653589793238462643383279};

int main() {
  // Will be an integer initialized to 3
  auto IntegerPi{Pi<int>};
}

Variable Templates with Custom Types

Any time the compiler encounters the Pi template being invoked with a type that it hasn't seen, it will use the template to create a new variable, passing the literal 3.141... to that type's constructor.

Given this happens at compile time, the type must have a constructor that can accept that argument at compile time. In the following example, we instantiate Pi<CustomType>, where CustomType has a constexpr constructor that can accept the double:

#include <iostream>

template <typename T>
constexpr T Pi{3.141592653589793238462643383279};

struct CustomType {
  constexpr CustomType(double InitialValue)
    : Value{int(InitialValue)} {}

  int Value;
};

int main() {
  auto Container{Pi<CustomType>};

  std::cout << "Container Value: "
    << Container.Value;
}
Container Value: 3.14159

Non-Typename Parameters

Variable templates are not restricted to just setting the type of the generated variable. Our templates can have multiple parameters, and use them as needed to create a variable.

Below, we define a template that creates integers whose value is initialized to the result of calling the + operator on two integer parameters:

template <int x, int y>
constexpr int Result{x + y};

int main() {
  int MySum{Result<1, 2>};
}

Given this calculation is done at compile time, we can ensure there is no runtime performance impact which is ideal if our calculation is expensive, or performed frequently.

However, it also means the expressions we use in our template variable must be evaluable at compile time. In the previous example, operator+(int, int) meets this criterion, so our template is valid.

FAQ: constexpr with Template Variables

In almost all scenarios, we will want to mark template variables as constexpr, preventing them from being modified. Similar to class templates, variable templates deduplicate themselves based on their arguments.

For example, if we have multiple invocations of Result<1, 2>, only a single variable will be generated. All references to Result<1, 2> will share that same variable. Therefore, if one of those locations modifies the variable, it can create unexpected behaviour elsewhere in our code:

#include <iostream>

template <int x, int y>
int Result{x + y}; // not const 

int main() {
  ++Result<1, 2>;

  // ...

  int MySum{Result<1, 2>};
  std::cout << "MySum: " << MySum;
}
MySum: 4

Marking our variable const (or constexpr) prevents this from happening:

template <int x, int y>
constexpr int Result{x + y};

int main() {
  ++Result<1, 2>;
}
error: increment of read-only variable 'Result<1, 2>'

It does not prevent variables created from our template from being modified A non-const variable like MySum can be created from a const template. Because MySum is not a reference, it is created by copying the value Result<1, 2>.

Therefore, modifications to MySum are not modifying Result<1, 2> - they’re acting on a copy:

#include <iostream>

template <int x, int y>
constexpr int Result{x + y};

int main() {
  int MySum{Result<1, 2>};
  std::cout << "MySum is now: " << ++MySum;

  std::cout << "\nResult<1, 2> is still: "
    << Result<1, 2>;
}
MySum is now: 4
Result<1, 2> is still: 3

consteval Functions (C++20)

Since the introduction of consteval in C++20, we now have an additional way to implement compile-time calculations. Our previous variable template could be replaced with this function, if preferred:

consteval int Result(int x, int y) {
  return x + y;
}

int main() {
  int MySum{Result(1, 2)};
}

This is more suitable for complex use cases, as it gives us the full flexibility of a function body. That can include if statements, local variables, and more to determine the resulting value.

It won’t work if our parameter list needs to include a typename, but we’ll see how we can solve that later in the chapter when we introduce function templates.

Summary

In this lesson, we learned about variable templates in C++. Here are the key takeaways:

  • Variable templates allow us to create variables with parameterized types, similar to class templates.
  • They provide flexibility and reusability by allowing the user to specify the desired type when instantiating the variable.
  • Variable templates can have multiple parameters and perform compile-time calculations.
  • It's recommended to mark variable templates as constexpr to prevent unexpected behavior due to shared instances.
  • Variable templates offer an alternative to creating multiple versions of variables for different types.
  • They enable us to create variables with custom types that have constexpr constructors.
  • Variable templates can be used for compile-time calculations, ensuring no runtime performance impact.

Was this lesson useful?

Next Lesson

Function Templates

Understand the fundamentals of C++ function templates and harness generics for more modular, adaptable code.
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Variable Templates

An introduction to variable templates, allowing us to create variables at compile time.

A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Templates
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Function Templates

Understand the fundamentals of C++ function templates and harness generics for more modular, adaptable code.
Illustration representing computer hardware
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved