Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is the documentation for an old version of Boost. Click here to view this page for the latest version.
PrevUpHomeNext

Literal Types and constexpr Support

There are two kinds of constexpr support in this library:

Declaring numeric literals

There are two backend types which are literals:

For example:

using namespace boost::multiprecision;

constexpr float128            f = 0.1Q   // OK, float128's are always literals in C++11

constexpr int128_t            i = 0;     // OK, fixed precision int128_t has no allocator.
constexpr uint1024_t          j = 0xFFFFFFFF00000000uLL;  // OK, fixed precision uint1024_t has no allocator.

constexpr checked_uint128_t   k = 1; // OK from C++14 and later, not supported for C++11.
constexpr checked_uint128_t   k = -1; // Error, as this would normally lead to a runtime failure (exception).
constexpr cpp_int             l = 2;  // Error, type is not a literal as it performs memory management.

There is also support for user defined-literals with cpp_int - these are limited to unchecked, fixed precision cpp_int's which are specified in hexadecimal notation. The suffixes supported are:

Suffix

Meaning

_cppi

Specifies a value of type: number<cpp_int_backend<N,N,signed_magnitude,unchecked,void> >, where N is chosen to contain just enough digits to hold the number specified.

_cppui

Specifies a value of type: number<cpp_int_backend<N,N,unsigned_magnitude,unchecked,void> >, where N is chosen to contain just enough digits to hold the number specified.

_cppiN

Specifies a value of type number<cpp_int_backend<N,N,signed_magnitude,unchecked,void> >.

_cppuiN

Specifies a value of type number<cpp_int_backend<N,N,signed_magnitude,unchecked,void> >.

In each case, use of these suffixes with hexadecimal values produces a constexpr result.

Examples:

// Any use of user defined literals requires that we import the literal-operators into current scope first:
using namespace boost::multiprecision::literals;
//
// To keep things simple in the example, we'll make our types used visible to this scope as well:
using namespace boost::multiprecision;
//
// The value zero as a number<cpp_int_backend<4,4,signed_magnitude,unchecked,void> >:
constexpr auto a = 0x0_cppi;
// The type of each constant has 4 bits per hexadecimal digit,
// so this is of type uint256_t (ie number<cpp_int_backend<256,256,unsigned_magnitude,unchecked,void> >):
constexpr auto b = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_cppui;
//
// Smaller values can be assigned to larger values:
int256_t c = 0x1234_cppi; // OK
//
// However, this only works in constexpr contexts from C++14 onwards:
constexpr int256_t d = 0x1_cppi; // Compiler error in C++11, requires C++14
//
// Constants can be padded out with leading zeros to generate wider types:
constexpr uint256_t e = 0x0000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFF_cppui; // OK
//
// However, specific-width types are best produced with specific-width suffixes,
// ones supported by default are `_cpp[u]i128`, `_cpp[u]i256`, `_cpp[u]i512`, `_cpp[u]i1024`.
//
constexpr int128_t f = 0x1234_cppi128; // OK, always produces an int128_t as the result.
constexpr uint1024_t g = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccc_cppui1024; // OK,
//  always produces an uint1024_t as the result.
//
// If other specific-width types are required, then there is a macro for generating the operators for these.
// The macro can be used at namespace scope only:
//
BOOST_MP_DEFINE_SIZED_CPP_INT_LITERAL(2048);
//
// Now we can create 2048-bit literals as well:
constexpr auto h = 0xff_cppi2048; // h is of type number<cpp_int_backend<2048,2048,signed_magnitude,unchecked,void> >
//
// Finally, negative values are handled via the unary minus operator:
//
constexpr int1024_t i = -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_cppui1024;
//
// Which means this also works:
constexpr int1024_t j = -g;   // OK: unary minus operator is constexpr.
constexpr arithmetic

The front end of the library is all constexpr from C++14 and later. Currently there are only two backend types that are constexpr aware: __float128 and cpp_int. More backends will follow at a later date.

Provided the compiler is GCC, type float128 support constexpr operations on all arithmetic operations from C++14, comparisons, abs, fabs, fpclassify, isnan, isinf, isfinite and isnormal are also fully supported, but the transcendental functions are not.

The cpp_int types support constexpr arithmetic, provided it is a fixed precision type with no allocator. It may also be a checked integer: in which case a compiler error will be generated on overflow or undefined behaviour. In addition the free functions abs, swap, multiply, add, subtract, divide_qr, integer_modulus, powm, lsb, msb, bit_test, bit_set, bit_unset, bit_flip, sqrt, gcd, lcm are all supported. Use of cpp_int in this way requires either a C++2a compiler (one which supports std::is_constant_evaluated() - currently only gcc-9 or clang-9 or later), or GCC-6 or later in C++14 mode. Compilers other than GCC and without std::is_constant_evaluated() will support a very limited set of operations: expect to hit roadblocks rather easily.

See compiler support for std::is_constant_evaluated;

For example given:

#include <boost/math/constants/constants.hpp> // For constant pi with full precision of type T.
// using  boost::math::constants::pi;

template <class T>
inline constexpr T circumference(T radius)
{
   return 2 * boost::math::constants::pi<T>() * radius;
}

template <class T>
inline constexpr T area(T radius)
{
   return boost::math::constants::pi<T>() * radius * radius;
}

We can now calculate areas and circumferences, using all compile-time constexpr arithmetic:

using boost::multiprecision::float128;

constexpr float128 radius = 2.25;
constexpr float128 c      = circumference(radius);
constexpr float128 a      = area(radius);

std::cout << "Circumference = " << c << std::endl;
std::cout << "Area = " << a << std::endl;

Note that these make use of the numeric constants from the Boost.Math constants library, which also happen to be constexpr. These usually have the full precision of the floating-point type, here 128-bit, about 36 decimal digits.

Calculating Hermite Polynomial coefficients at compile time

For a more interesting example, in constexpr_float_arithmetic_examples.cpp we define a simple class for constexpr polynomial arithmetic:

template <class T, unsigned Order>
struct const_polynomial;

Given this, we can use recurrence relations to calculate the coefficients for various orthogonal polynomials - in the example we use the Hermite polynomials. Only the constructor does any work - it uses the recurrence relations to calculate the coefficient array:

template <class T, unsigned Order>
class hermite_polynomial
{
   const_polynomial<T, Order> m_data;

 public:
   constexpr hermite_polynomial() : m_data(hermite_polynomial<T, Order - 1>().data() * const_polynomial<T, 1>{0, 2} - hermite_polynomial<T, Order - 1>().data().derivative())
   {
   }
   constexpr const const_polynomial<T, Order>& data() const
   {
      return m_data;
   }
   constexpr const T& operator[](std::size_t N)const
   {
      return m_data[N];
   }
   template <class U>
   constexpr T operator()(U val)const
   {
      return m_data(val);
   }
};

Now we just need to define H0 and H1 as termination conditions for the recurrence:

template <class T>
class hermite_polynomial<T, 0>
{
   const_polynomial<T, 0> m_data;

 public:
   constexpr hermite_polynomial() : m_data{1} {}
   constexpr const const_polynomial<T, 0>& data() const
   {
      return m_data;
   }
   constexpr const T& operator[](std::size_t N) const
   {
      return m_data[N];
   }
   template <class U>
   constexpr T operator()(U val)
   {
      return m_data(val);
   }
};

template <class T>
class hermite_polynomial<T, 1>
{
   const_polynomial<T, 1> m_data;

 public:
   constexpr hermite_polynomial() : m_data{0, 2} {}
   constexpr const const_polynomial<T, 1>& data() const
   {
      return m_data;
   }
   constexpr const T& operator[](std::size_t N) const
   {
      return m_data[N];
   }
   template <class U>
   constexpr T operator()(U val)
   {
      return m_data(val);
   }
};

We can now declare H9 as a constexpr object, access the coefficients, and evaluate at an abscissa value, all at compile-time using constexpr arithmetic:

constexpr hermite_polynomial<float128, 9> h9;
//
// Verify that the polynomial's coefficients match the known values:
//
static_assert(h9[0] == 0);
static_assert(h9[1] == 30240);
static_assert(h9[2] == 0);
static_assert(h9[3] == -80640);
static_assert(h9[4] == 0);
static_assert(h9[5] == 48384);
static_assert(h9[6] == 0);
static_assert(h9[7] == -9216);
static_assert(h9[8] == 0);
static_assert(h9[9] == 512);
//
// Define an abscissa value to evaluate at:
constexpr float128 abscissa(0.5);
//
// Evaluate H_9(0.5) using all constexpr arithmetic, and check that it has the expected result:
static_assert(h9(abscissa) == 6481);

See constexpr_float_arithmetic_examples.cpp for working code.

Also since the coefficients to the Hermite polynomials are integers, we can also generate the Hermite coefficients using (fixed precision) cpp_ints: see constexpr_test_cpp_int_6.cpp.

constexpr Factorials

We can also generate integer factorials in constexpr_test_cpp_int_5.cpp like so:

template <class T>
constexpr T factorial(const T& a)
{
   return a ? a * factorial(a - 1) : 1;
}

and validate the result:

constexpr uint1024_t f1 = factorial(uint1024_t(31)); // Factorial 31!
static_assert(f1 == 0x1956ad0aae33a4560c5cd2c000000_cppi); // Expected result as an Boost.Multiprecision integer literal. 
Random constexpr values

Another example in constexpr_test_cpp_int_7.cpp generates a fresh multiprecision random number each time the file is compiled. It includes an C++ template implementation of the KISS random number algorithm by George Marsaglia for cpp_int integers.

constexpr uint1024_t rand = nth_random_value<uint1024_t>(1000);
std::cout << std::hex << rand << std::endl;

See also the random number generation section.


PrevUpHomeNext