0%

constexpr in the Standard Library in C++

This is my learning notes from the course - Mastering C++ Standard Library Features.

constexpr in the Standard Library

  • constexpr in the Standard Library (C++11/14/17)
  • Use case examples

Why?

  • Having the Standard Library support constexpr algorithms and data structures out-of-the-box:
    • Allows people to reuse familiar abstractions for compile-time programming
    • Reduces separation of the community (For example: constexpr libraries versus run-time libraries)

C++11

  • For C++11, the standard committee was very conservative with adding constexpr to Standard Library facilities for several reasons:
    • Work was mostly focused on constexpr as a language feature
    • Implementation freedom would have been severely limited (the C++11 constexpr restrictions were severe)
    • Evaluating which functions could or could not be marked as constexpr required a huge effort

  • Constants: std::numeric_limits, std::char_traits, <random> header engine constants, and more
  • Many default constructors (For example: std::pair, std::tuple)
  • std::bitset: construction from unsigned int, size() and operator[]
  • std::ratio and std::chrono::{duration, time_point}
  • std::array::size
  • std::atomic<T> construction from T

C++14

  • Relaxed constexpr rules in C++14 paved the way for a more constexpr Standard Library:
    • std::pair and std::tuple‘s constructors, data access, and comparisons
    • std::forward, std::move, and std::move_if_noexcept
    • std::complex‘s comparisons and literal suffixes
    • std::min and std::max
    • <functional> utilities like std::less and std::greater

C++17

  • In C++17, constexpr was already part of the original design of several new library features:
    • std::string_view
    • std::optional
    • std::variant
    • Uniform container access: std::size, std::empty, std::data

Example: Counting std::array Elements Matching a Predicate

1
2
3
4
5
6
7
8
template <typename T>
constexpr
std::size_t count_true(const std::array<bool, N>& a)
{
std::size_t result = 0;
for(const auto& x : a) if(x) ++result;
return result;
}
  • Can be used both at run-time and compile-time
  • In C++20, you can use std::count or std::count_if

Example: Calculating Buffer Size for Multiple Types

1
2
3
4
5
template <typename ... Ts>
using storage_for = std::aligned_storage_t<
std::max({sizeof(Ts) ... }),
std::max({alignof(Ts) ... })
>;
  • std::aligned_storage<Len, Align> is uninitialized storage for any object whose size is at most Len and suitable for Align
  • std::max(std::initializer_list<T>) is constexpr since C++14

  • This could be used to implement a type-safe union alternative (For example, std::variant)
    1
    2
    3
    4
    5
    6
    template <typename ... Ts>
    class variant
    {
    storage_for<Ts ...> _storage;
    // ...
    };
    • In face, the Standard Library provides std::aligned_union which is implemented similarly to storage_for

Example: Parsing Strings at Compile-Time

1
2
3
4
5
6
7
8
constexpr auto parse_person(std::string_view s)
{
const auto mid = s.find_first_of(';');
return std::make_tuple(
s.substr(0, mid),
s.substr(mid + 1, s.size() - mid)
);
}
  • std::make_tuple marked constexpr since C++14
  • std::string_view introduced in C++17

1
2
3
4
5
6
constexpr auto name = "Bjarne;Stroustrup";
constexpr auto fst_name = std::get<0>(parse_person(name));
constexpr auto lst_name = std::get<1>(parse_person(name));

static_assert(fst_name == std::string_view{"Bjarne"});
static_assert(lst_name == std::string_view{"Stroustrup"});
  • std::get marked constexpr since C++14
  • std::string_view comparisons are constexpr
  • Note that parse_person could also be used at run-time

Summary

  • Learned that the Standard Library is increasingly getting more constexpr-friendly
  • Understood that since C++14, many general-purpose utilities can be safely used in constexpr functions (For example: std::pair, std::tuple, std::min, std::max)
  • Learned that in C++17 and the upcoming C++20, even more Standard Library algorithms and data structures will be marked as constexpr

Guidelines

  • Use std::pair or std::tuple when returning multiple values from constexpr functions
  • std::string_view is constexpr-friendly: use it when writing functions that need to parse/validate strings
  • Test your constexpr functions at compile-time by using static_assert