C++14 support has been shipped as part of the ARM and MSP430 18.1.1.LTS; and C6000 8.3.0 release. This article serves to introduce various new features in the language, as well as to provide small examples of how they might apply to embedded application development.

The constexpr type specifier

The constexpr specifier was introduced in C++11. While similar in purpose to the const qualifier, the guarantee provided by constexpr is much stronger. While const provides a guarantee that an object shall not be modified from its original value, constexpr asserts that the associated entity can be evaluated at compile time, resulting in a truly constant value.

constexpr may be applied to both variables and functions.

Variables and Data Members

constexpr variables are helpful as type-safe replacements for macros, and can be used to provide descriptive names for what would otherwise be a 'magic' number or value, without relying upon the compiler to optimize away the variable itself.

constexpr double PI = 3.1415926535;
double circumference(double radius) { return PI * (2 * radius); }

constexpr variables can be used in place of macros for things like memory-mapped registers, ensuring that:

  • The address has an actual name, rather than being the result of token replacement
  • The address has an associated type, which could potentially disambiguate various fields and operations
struct mmr {
    unsigned int a:1;
    unsigned int b:12;
    unsigned int c:10;
    unsigned int d:9;
};

constexpr volatile struct mmr *MMR = (volatile struct mmr *)(0x1234abcd);
/* Now code can say 'MMR->a' rather than relying on a function-like macro to
   access the first bit */

A more advanced usage for constexpr variables is to declare static data members in a class type. This ties the compile-time value to the type. This is useful when utilizing a generic algorithm with different types, when a constant is the only thing that changes.

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

template<class T> struct magic_number { };

// Template specializations for int32_t and int64_t
template<>
struct magic_number<int32_t> {
    static constexpr int32_t value{10};
    static constexpr const char *fmt{"%" PRId32};
};
template<>
struct magic_number<int64_t> {
    static constexpr int64_t value{20};
    static constexpr const char *fmt{"%" PRId64};
};

// Variable templates
template <class T>
constexpr T magic_number_v = magic_number<T>::value;
template <class T>
constexpr const char *magic_fmt_v = magic_number<T>::fmt;

template <class T>
void foo() { printf(magic_fmt_v<T>, magic_number_v<T>); }

int main() {
    foo<int32_t>(); // Prints 10
    printf("\n");
    foo<int64_t>(); // Prints 20
    printf("\n");
}

In an embedded application, this type of generic programming can help separate configurations and values for various boards or subtargets. Instead of int32_t and int64_t specializations of magic_number, the application can instead use enumeration values for each particular subtarget, which is chosen at compile time.

Functions

constexpr functions provide a way to implement compile-time constants that require some amount of algorithmic analysis to generate. A call to a constexpr function is interpreted by the compiler during compilation, and its return value value replaces the call in the code.

/* More powerful than macros because these are now type-safe, can be
   overloaded, and may also be templates. */
constexpr unsigned int low(unsigned int i) { return i & 0xffff; }
constexpr unsigned int high(unsigned int i) { return low(i >> 16); }
int max(int a, int b) { return ((a > b) ? a : b); }
#include <array>

enum color {
    red,
    blue,
    orange,
    green,
};
/* This array is a compile-time constant. As long as its address is not
   taken, it will not generate any code by itself. */
constexpr std::array<std::pair<color, int>, 4> pair_array{
    { {red, 0x255}, {blue, 0x10}, {orange, 0x1000}, {green, 0x0}, }
};

constexpr int get_color_val(color c)
{
    /* constexpr functions may contain loops */
    for (int i = 0; i < pair_array.size(); i++)
        if (pair_array[i].first == c) return pair_array[i].second;
    return -1;
}

int main()
{
    /* Go search for all the colors and extract their associated int values
       as constant expressions. */
    constexpr int color_vals[pair_array.size()] = {
        get_color_val(red), get_color_val(blue),
        get_color_val(orange), get_color_val(green),
    };
    /* Validate the values */
    static_assert(color_vals[0] ==  0x255, "Value 0 is incorrect");
    static_assert(color_vals[1] ==   0x10, "Value 1 is incorrect");
    static_assert(color_vals[2] == 0x1000, "Value 2 is incorrect");
    static_assert(color_vals[3] ==    0x0, "Value 3 is incorrect");
}

More Information

The CPPReference Page for constexpr