Click here to Skip to main content
Click here to Skip to main content

Constants and Constant Expressions in C++11

By , 11 Jul 2012
Rate this:
Please Sign up or sign in to vote.

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.

// const1_include.h
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():

//const1_include.cpp
#include "const1_include.h"
double fx() { return x;}
double fy() { return A::y;}

Here the main cpp-file:

// const1.cpp
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]; // error!

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)]; // error!

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; // linking error!
    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:

//my_class.h
class A 
{ 
public:        
    static const double c; 
}; 
//----------------------------

//my_class.cpp
#include "my_class.h"
const double A::c = 1000.0; 
//-----------------------------

//my_class2.h
class B
{ 
public:     
    static const double d;
}; 
//-----------------------------

//my_class2.cpp
#include "my_class.h"
#include "my_class2.h"
 
const double B::d = A::c;
//-----------------------------

//static_const_inside_class.cpp
#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:

//my_class.h
class A 
{ 
public:     
    static constexpr double c = 1000.0;
}; 
//--------------------------------------

//my_class.cpp
#include "my_class.h"
 
constexpr double A::c;
//--------------------------------------

//my_class2.h
#include "my_class.h"
class B
{ 
public:     
    static constexpr double d=A::c;
}; 
//---------------------------------------

//my_class2.cpp
#include "my_class2.h"
 
constexpr double B::d;
//-----------------------------------------

//static_constexpr_inside_class.cpp 

#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:

  1. 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;
  2. 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 char 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); }  // not a constexpr operator  
}; 

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; // s1 is of type ConstString       
 std::string s2 = s1; // automatic conversion to std::string is allowed
 constexpr ConstString s3 = s1; // copying is allowed

References

[1] Working Draft, Standard for Programming Language C++

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mikhail Semenov
Software Developer (Senior)
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMichael Haephrati מיכאל האפרתי4-Dec-12 2:27 
GeneralMy vote of 5 PinmemberLWessels17-Jul-12 10:14 
GeneralMy vote of 5 PinmemberJason Leatherface16-Jul-12 11:48 
GeneralMy Vote of 5. PinmemberA_K_10-Jul-12 23:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 11 Jul 2012
Article Copyright 2012 by Mikhail Semenov
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid