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;
//
// 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 back end 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.

For example given:

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 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 Math library, which also happen to be constexpr.

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 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:
//
static_assert(h9(abscissa) == 6481);

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

We can also generate factorials (and validate the result) like so:

template <class T>
constexpr T factorial(const T& a)
{
   return a ? a * factorial(a - 1) : 1;
}
constexpr uint1024_t f1 = factorial(uint1024_t(31));
static_assert(f1 == 0x1956ad0aae33a4560c5cd2c000000_cppi);

Another example in constexpr_test_cpp_int_7.cpp generates a fresh multiprecision random number each time the file is compiled.


PrevUpHomeNext