Introduction
In this article the issues concerning constant definitions (const) and constant expressions (contexpr) are discussed. The examples, concerning constants, run in GCC 4.7.0, Visual Studio 2008, Visual Studio 2010 and Visual Studio 11. The examples dealing with constexpr
run in GCC 4.7.0, but not in Visual Studio (they are not implemented in Visual C++ 11 yet).
Constants in the Global or Namespace Scope
Constants in the global or namespace scope (if not declared extern) have internal linkage. This means that such constants can be defined in an h-file and used in various modules without any errors concerning duplicate definitions, etc. You may put the word static
, but this is superfluous.
const double x = 3.2;
namespace A
{
const double y = 5.0;
}
double fx();
double fy();
Here is the cpp-file that implements fx()
and fy()
:
#include "const1_include.h"
double fx() { return x;}
double fy() { return A::y;}
Here the main cpp-file:
include <iostream>
#include "const1_include.h"
#include <string>
void f(const std::string& name, const int& x)
{
std::cout << name << x << std::endl;
}
int main()
{
f("x: ", fx());
f("y: ", fy());
f("x: ", x);
f("y: ", A::y);
return 0;
}
This program runs both in Visual Studio and GCC 4.7.0. The issue is that constants, in contrast to variables, have internal linkage.
The problem is when we start using constants in places, where they have to be defined at compile time, for example as the size of an array in the array definition:
double a[N];
Here N should be defined at compile time. In order to guarantee that some special conditions can be met. Let’s look at some problems. If a double
constant is defined, it cannot be used in the array definition:
const double p = 7.2;
int a[(int)p];
If we define the following template function:
template<class T, int N>
int array_size(const T (&a)[N])
{
return N;
}
it can be used to determine the size of an array:
int b[] = {1,2,3};
std::cout << array_size(b) << std::endl;
But it cannot be used to define another array:
double c[array_size(b)];
The reason is that these values are not defined at compile time.
In C++11 it is possible to define constants, functions and classes so that they can be used to define other objects at compile time. A special keyword, constexpr
, is used to define such constructs. In general, expressions available at compile time are called constant expressions
. There are special conditions imposed on functions and classes so that they can be defined with the constexpr
keyword. In the above-mentioned cases, the code may be modified like that (valid in GCC 4.7.0):
constexpr double p = 7.2;
int a[(int)p];
template<class T, int N>
constexpr int array_size(const T (&a)[N])
{
return N;
}
int b[] = {1,2,3};
double c[array_size(b)];
Constants in Class Scope
Static constants defined in class scope have external linkage, which means there should be one module where they are fully defined. Integer static constants can have their values specified immediately in the declaration:
struct S
{
static const int n = 25;
double a[n];
};
This works fine for an array, but if a reference to a constant is used anywhere in the program, the constant should be defined on the namespace level, like this:
const int S::n;
Sometimes, a reference can be required in unexpected circumstances. Consider the following program:
#include <iostream>
struct S
{
static const int n = 25;
static const int m = 50;
double a[n];
};
int main()
{
std::cout << std::max(S::m, S::n) << std::endl; return 0;
}
GCC 4.7.0 will give an “undefined reference” error. To correct the problem, you should put the following two lines before “int main()”:
const int S::n;
const int S::m;
On the other hand, instead of static int
constants you can use enumerated types: it is much easier and you won't get any surprises. Consider the following program:
#include <iostream>
#include <iomanip>
struct A
{
enum { n = 27, m = 51, p = -82 };
enum: long long int { c = 0x12345678ABCDEF03 };
enum: char { z = 'z' };
double a[n];
};
void f(const int& k)
{
std::cout << k << std::endl;
}
void g(int k)
{
std::cout << k << std::endl;
}
int main()
{
std::cout << std::max(A::m, A::n) << std::endl;
f(A::m);
f(A::n);
f(A::p);
g(A::m);
g(A::n);
g(A::p);
std::cout << "c: " << std::hex << A::c << std::dec << std::endl;
std::cout << "z: " << A::z << " " << (char)A::z << std::endl;
return 0;
}
In this program, all the "constants" behave naturally, and it's easy to define them - without any extra effort. The program will print:
51
51
27
-82
51
27
-82
c: 12345678abcdef03
z: 122 z
The only small problem is the value z.
Look at the last line: we have to convert the value to char
type explicitly in order to print is as a character. By default, it is printed as an integer because z
is an enumerator.
With other types, the situation is different. If we use an ordinary const
, the values cannot be specified in the declaration, but only in the definition in the global namespace scope. For example:
struct S
{
static const double p;
};
const double S::p = 7.5;
This works well for a single module, but it is not always good, when several modules are defined. Consider the following program, composed of several modules:
class A
{
public:
static const double c;
};
#include "my_class.h"
const double A::c = 1000.0;
class B
{
public:
static const double d;
};
#include "my_class.h"
#include "my_class2.h"
const double B::d = A::c;
#include <iostream>
#include "my_class.h"
#include "my_class2.h"
const double div2 = B::d/10.0;
int main()
{
std::cout << A::c << std::endl;
std::cout << B::d << std::endl;
std::cout << div2 << std::endl;
return 0;
}
Contrary to most programmers’ expectations, the program will often output:
1000
1000
0
The last value will often (not always, it depends upon the system) will be 0, not 100. The reason is, that static constants, whose value are initialized in other modules, are not guaranteed to get values before the execution of main
, which means that the B::d
value may not be available when div2 is defined. This can lead to a lot of errors.
Now, in C++11, constexpr
can help solve these problems. First of all, constexpr
can be used for both int
and double
types and they can both be initialized inside the class. The values are guaranteed to be available at compile time. So, you are safe to use them. You may rewrite the above modules as follows:
class A
{
public:
static constexpr double c = 1000.0;
};
#include "my_class.h"
constexpr double A::c;
#include "my_class.h"
class B
{
public:
static constexpr double d=A::c;
};
#include "my_class2.h"
constexpr double B::d;
#include <iostream>
#include "my_class2.h"
constexpr double div2 = B::d / 10.0;
int main()
{
std::cout << A::c << std::endl;
std::cout << B::d << std::endl;
std::cout << div2 << std::endl;
return 0;
}
This program will print, according to common expectations:
1000
1000
100
Consexpr Functions
The general problem is that is you define constexpr
objects (constexpr
constants), as opposed to const
objects (ordinary constants), you may only initialize them with constant expressions, where ordinary functions are not allowed, but only calls to constexpr
functions. A constexpr function
should satisfy the following conditions:
- its body must have only one statement, which should be a
return
with a non-void value (some asserts are allowed as well); - the value of the function and it’s parameters (if any) should be of types allowed as constexpr.
In order to write sophisticated algorithms, the only choice you have is to use recursion. There is a minimum limit on the depth of the recursion, which should be allowed by an implementation: 512.
Constexpr functions can be mutually recursive, but the function must be fully defined before it is used to define a constexpr object. A constexpr function may be called with non-constexpr arguments (for example, variables), but in this case its value will not be a constant expression. You may not define a constexpr function with the same name and parameters as another non-constexpr function.
The C++11 Standard does not require functions in <cmath> to be constexpr, which means that, as a general rule, functions, like sin(x) and sqrt(x), cannot be used in constant expressions. But, in GCC 4.7.0, they are defined as contsexpr functions, which is an extension to the Standard. If, in a particular implementation, sqrt is not defined as constexpr, you may define your own constexpr function, but you have to call it differently, say Sqrt. Such definition may look like this (we have defined the auxiliary functions Abs and Sqrt_impl):
constexpr double Abs(double x)
{
return (x > 0.0 ? x : -x);
}
constexpr double Sqrt_impl(double y, double x1, double x2, double eps)
{
return (Abs(x1 -x2) < eps ? x2 : Sqrt_impl(y, x2, (x2+y/x2)*0.5, eps));
}
constexpr double Sqrt(double y)
{
return Sqrt_impl(y, 0, y*0.5 + 1.0, 1e-10);
}
Here is another example, which shows contsexpr definition of cos(x) and sin(x):
constexpr double SinCos_impl(double x2, double n, double u)
{
return (Abs(u) < 1e-10 ? u : u + SinCos_impl(x2, n+2.0, -u*x2/(n * (n + 1))));
}
constexpr double Cos(double x);
constexpr double Sin(double x)
{
return (Abs(x) < 0.5 ? SinCos_impl(x*x, 2.0, x) : 2 * Sin(x * 0.5) * Cos(x * 0.5));
}
constexpr double Sqr(double x)
{
return x*x;
}
constexpr double Cos(double x)
{
return (Abs(x) < 0.5 ? SinCos_impl(x*x, 1.0, 1.0) : Sqr(Cos(x * 0.5)) - Sqr(Sin(x * 0.5)));
}
Function formal parameters are never specified as constexpr.
Types that Can Be Used in Constant Expressions
The C++11 Standard defines so called literal types
, which can be used in constant expressions. A literal type
is:
• an arithmetic type (an integral, floating-point, character type or the bool type);
• a pointer type;
• a reference type to a literal type (for example, int& or double&);
• an array of literal type;
• a literal class.
A literal class has the following properties:
• it does not have a user-defined destructor and all of its base classes do not have user-defined destructors;
• its non-static data members and base classes are of literal types;
• all its non-static data members are initialized, using constant expressions;
• it satisfies one of the following two conditions:
- it has no user-provided constructors, no initializers for non-static data members, no private or protected non-static data members, no base classes, and no virtual functions;
- it has at least one constexpr constructor or constructor template, in addition to possible copy and move constructors; a constexpr constructor always has an empty body, but allows initialization of all the class members.
So, constructors can be defined as constexpr. Member functions can be constexpr, if they are not virtual. In constant expressions, you may use pointers, but you are not allowed to access data allocated on the heap using new
.
Here is an example of a literal class and its use:
struct RGB
{
unsigned char r, g, b;
};
constexpr RGB Red{255, 0,0};
constexpr RGB Green{0, 255,0};
constexpr RGB Blue{0, 0,255};
Non-static member objects are never declared as constexpr.
But if you’d like to add some operations to the RGB class you may modify the code as follows:
struct RGB
{
unsigned char r, g, b;
constexpr RGB(unsigned char x, unsigned char y, unsigned char z): r(x),g(y),b(z) {}
};
constexpr RGB Red{255, 0,0};
constexpr RGB Green{0, 255,0};
constexpr RGB Blue{0, 0,255};
constexpr unsigned char Limit255(unsigned x)
{
return ( x > 255 ? 255 : x);
}
constexpr RGB operator+(const RGB& x, const RGB& y)
{
return RGB(Limit255(x.r + y.r), Limit255(x.g + y.g), Limit255(x.b + y.b));
}
constexpr RGB Yellow = Red+Green;
constexpr RGB Magenta = Red+Blue;
constexpr RGB Cyan = Green+Blue;
How to Initialize Member Arrays in Consexpr Classes
The difficulty is that std::string
and std::vector
and other containers cannot be used in constant expressions. You have to use only literal types. Let’s define a literal array class: we’ll call it ConstArray
, which is an array of fixed size, which can be used in constant expressions.
The challenge is to initialize an array member in a class, which depends upon parameters. You are not allowed to use non-constexpr functions or constructors. Let’s first consider a simple example of an array of three elements:
struct ConstArray3
{
const double a[3];
constexpr ConstArray3(double a1, double a2, double a3):a{a1,a3,a3} {}
constexpr int size() { return 3;}
constexpr double operator[](int i) { return a[i];}
};
The braced initialization list helped to achieve that. If we want to consider an array of arbitrary number of elements, then variadic templates will help:
template <class ElemType, int N>
class ConstArray
{
const ElemType a[N];
public:
template <class ... T> constexpr ConstArray(T ... p):a {p...} {}
constexpr int size() { return N;}
constexpr ElemType operator[](int i)
{ return (i < 0 || i >= N ? throw "ERROR: out of range": a[i]);}
};
Now we can easily use it as follows:
constexpr ConstArray<double, 3> a{1.0, 2.5, 3.0};
constexpr ConstArray<char, 4>s{'a','b','c','d'};
How to Initialize Member Strings in Constexpr Classes
Since we cannot use std::string
in constant expressions we can either use const char*
or const char[]
. Let’s consider the first option:
class ConstString
{
const char* s;
const int n;
public:
constexpr ConstString(const char* s1, int n1): s(s1),n(n1-1){};
constexpr char operator[](int j)
{
return ( j < 0 || j >= n ? throw "ERROR: ConstString index out of bounds" : s[j]);
}
constexpr int size() { return n; }
constexpr const char* c_str() { return s;}
operator std::string() const { return std::string(s,n); } };
The last operator is not a constant expression and cannot be used in constant expressions. It's important to know that constexpr member functions do not have to be qualified as const
: the const
qualifier is automatically assumed.
It’s convenient to create a wrapper for string literals:
template<int N>
constexpr ConstString Str(const char (&s1)[N])
{
return ConstString(s1, N);
}
This allows us to create constexpr objects as follows:
constexpr auto cs(Str("Oranges"));
constexpr auto cs2(Str(" and Apples"));
constexpr auto empty(Str(""));
We may even create a structure using ConstString members:
class PersonalData
{
ConstString name;
ConstString surname;
public:
template <int NameLength, int SurnameLength>
constexpr PersonalData(const char (&name1)[NameLength], const char(&surname1)[SurnameLength]):
name(name1,NameLength),surname(surname1,SurnameLength) {}
constexpr ConstString getName() { return name;}
constexpr ConstString getSurname() { return surname;}
};
template <int NameLength, int SurnameLength>
constexpr PersonalData CreatePersonalData(const char (&name1)[NameLength], const char(&surname1)[SurnameLength])
{
return PersonalData(name1, surname1);
}
constexpr auto JohnSmith = CreatePersonalData("John","Smith");
constexpr auto MaryGreen = CreatePersonalData("Mary","Green");
If we’d like to compare ConstString values it is also possible. Since we have to rely only on recursive functions if we want our values to remain in the realm of constant expressions (Here again we need to define an auxiliary function):
constexpr int Compare_Impl(const ConstString& s1, const ConstString& s2, int i)
{
return (i >= s1.size() && i >= s2.size() ? 0 :
(i >= s1.size() ? -1 :
(i >= s2.size() ? 1 : Compare_Impl(s1,s2, i+1))));
};
constexpr int Compare(const ConstString& s1, const ConstString& s2)
{
return Compare_Impl(s1,s2,0);
}
It is also possible to create a use-defined literal, which will allow to write string literals (instead of using the function Str) whose values will be ConstString:
constexpr ConstString operator "" _S(const char* s, std::size_t n) { return ConstString(s,n+1);}
This enables you to write compact and clear code:
constexpr auto s1 = "An apple tree"_S;
std::string s2 = s1;
constexpr ConstString s3 = s1;
References
[1] Working Draft, Standard for Programming Language C++
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf