Some applications make use of immutable, shared constants such as pi or Boltzmann’s constant.
The question arises on how to use them across multiple translation units without violating the ODR.
C++ has offered three answers over the years, and only one of them is the right default in modern code.
Three things to take away:
constexprconstants in a header give every translation unit its own copy.extern constshares one definition but loses constant-expression use at the call site.inline constexpr(C++17) gives you both: one definition, fullconstexprsemantics
1st Solution – Header-Defined constexpr Constants
- Create a header file to store constants
#ifndef CONSTANTS_H
#define CONSTANTS_H
// For extra organization, store them in a namespace
namespace constants
{
constexpr double pi { 3.14159 };
constexpr double boltzmann { 1.380649e-23 }; // Joules per Kelvin (J/K)
// Insert other constants
}
#endif
2. Import the header file for any files that require the use of the constants.
#include <iostream>
#include "constants.h"
int main()
{
// Temperature in Kelvin
double temperature { 298.15 };
// Invoke the constant using "::"
double kineticEnergy = 1.5 * constants::boltzmann * temperature;
}
This is suitable for smaller programs. However, since the constants are constexpr they have internal linkage. If we need to import CONSTANTS into 22 files, each translation units receives it’s own unique copy. The result is that the constants are copied 22 times.
This is problematic for two reasons:
- If the constant is changed, the compiler has to recompile every single file that imports
CONSTANTS - If the constants are large (say a lookup table or a long string literal), the compiler cannot optimize away the variable. This leads to significant memory usage.
2nd solution – Applying extern
To remove the costs of duplication, we can apply extern to the constants.
This gives the variables external linkage, meaning each file doesn’t have to instantiate their own version.
To do this, we have a .h file that contains the variable definitions and a .cpp for the implementation. It’s usage in main() remains the same.
// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
// For extra organization, store them in a namespace
namespace constants
{
extern const double pi;
extern const double boltzmann; // Joules per Kelvin (J/K)
// Insert other constants
}
#endif
// constants.cpp
#ifndef CONSTANTS_H
#define CONSTANTS_H
// For extra organization, store them in a namespace
namespace constants
{
extern constexpr double pi { 3.14159 };
extern constexpr double boltzmann { 1.380649e-23 }; // Joules per Kelvin (J/K)
// Insert other constants
}
#endif
However, this strategy comes with downsides
- Every other file that uses the constants relies on the forward declaration. This isn’t
constexprthus if the constants are used outside ofconstants.cppit cannot be apart of a constant expression - We require 2 separate files
3rd solution – inline constexpr variables (C++17)
#042 – inline is no longer about performance discussed the use of inline to functions. As of C++17, they can be applied to variables.
An inline variable may be defined in a header and included from any number of translation units without violating the ODR — the linker accepts the duplicate definitions and merges them into a single entity
// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
// For extra organization, store them in a namespace
namespace constants
{
inline constexpr double pi;
inline constexpr double boltzmann; // Joules per Kelvin (J/K)
// Insert other constants
}
#endif
However, if a variable changes in constants.h, this triggers a recompilation for every file that imports it.
In Conclusion
If you need global constants and your compiler is C++17 capable, prefer defining inline constexpr global variables in a header file.