trainings/AdvancedCppV2/Presentation/moder_cpp_cpp17_constexpr.md

12 KiB

constexpr - one of the most undervalued feature in modern C++ (1)

  • constexpr was introduced in C++11
    • constexpr values are know during compilation process
    • Function also could be constexpr with some restriction:
      • they may only have exactly one statement and this statement had to be a return statement
      • not allowed to have any side effects, like if-else
      • not allowed to have exception and try-catch block
      • can use ternary operator
      • Usually wrote recurent rather then iterative functions
      • Functions behave like normal functions (not solved during compilation) when working with non-constexpr arguments
    • Class/ struct could have constexpr C'tor and member functions
  • In C++14 introduced:
    • Less restrictions for functions:
      • more then one return
      • can use if-else

constexpr - one of the most undervalued feature in modern C++ (2)

  • In C++17 introduced:
    • constexpr class string_view
    • constexpr std::array
    • constexpr iterator for array and std::begin, std::end functions
    • constexpr lambda
  • In C++20 introduced:
    • constexpr STL algorithms
    • constexpr std::vector and std::string and their iterators. (not supported by clang and gcc. Surprisely its supported by MSVC STL)
    • Allow to use try-catch in constexpr functions
    • Allow to allocate and deallocate values on heap, but need to free them before exit function
    • Allow to create virtual functions
    • Allow to use asm code block

C++11 constexpr function

#include <cstddef>

constexpr bool isLower(char c) {
    return c >= 'a' && c <= 'z';
}

template <typename T, size_t N>
constexpr size_t countLower(const T (&str)[N], size_t current, size_t counter) {
    return current == N
               ? counter
           : isLower(str[current])
               ? countLower(str, current + 1, counter + 1)
               : countLower(str, current + 1, counter);
}

int main() {
    static_assert(9 == countLower("Ala has a cat", 0, 0));
}
xor    eax,eax
ret    
nop    WORD PTR cs:[rax+rax*1+0x0]
nop    DWORD PTR [rax]
___

C++14 constexpr function

constexpr bool isLower(char c) {
    return c >= 'a' && c <= 'z';
}

template <typename T, size_t N>
constexpr size_t countLower(const T (&str)[N]) {
    size_t counter = 0;
    for (auto c : str) {
        if (isLower(c)) {
            ++counter;
        }
    }

    return counter;
}

int main() {
    static_assert(9 == countLower("Ala has a cat"));
}
xor    eax,eax
ret    
nop    WORD PTR cs:[rax+rax*1+0x0]
nop    DWORD PTR [rax]
___

C++17 constexpr function (1)

template <typename T, size_t N>
constexpr size_t countLower(const T (&str)[N]) {
    constexpr auto isLower = [](char c) { return c >= 'a' && c <= 'z'; };
    size_t counter = 0;

    for (auto c : str) {
        if (isLower(c)) {
            ++counter;
        }
    }

    return counter;
}

int main() {
    static_assert(9 == countLower("Ala has a cat"));
}
xor    eax,eax
ret    
nop    WORD PTR cs:[rax+rax*1+0x0]
nop    DWORD PTR [rax]
___

C++17 constexpr function (2)

constexpr size_t countLower(const std::string_view& str) {
    constexpr auto isLower = [](char c) { return c >= 'a' && c <= 'z'; };
    size_t counter = 0;

    for (size_t i = 0 ; i < str.size() ; ++i) {
        if (isLower(str[i])) {
            ++counter;
        }
    }

    return counter;
}

int main() {
    static_assert(9 == countLower("Ala has a cat"));
}
xor    eax,eax
ret    
cs nop WORD PTR [rax+rax*1+0x0]
nop    DWORD PTR [rax]
___

C++20 constexpr function

constexpr size_t countLower(const std::string& str) {
    constexpr auto isLower = [](char c) { return c >= 'a' && c <= 'z'; };
    size_t counter = 0;

    for (size_t i = 0 ; i < str.size() ; ++i) {
        if (isLower(str[i])) {
            ++counter;
        }
    }

    return counter;
}

int main() {
    static_assert(9 == countLower(std::string("Ala has a cat")));
}
___

C++20 constexpr function - algorithms

constexpr size_t countLower(const std::string& str) {
    constexpr auto isLower = [](char c) { return c >= 'a' && c <= 'z'; };
    return std::count_if(cbegin(str), cend(str), isLower);
}

int main() {
    static_assert(9 == countLower(std::string("Ala has a cat")));
}
___

C++20 constexpr - allocation on heap (1)

template <typename T, size_t N>
constexpr std::array<T, N> generatePseudoRandom(int from, int to) {
    std::array<T, N> arr;
    const int seed = ((N << 1) ^ (from << to) ^ to) ^ ((from + to) ^ (N << 3));
    const size_t size = to - from;
    auto tmp = new int[size];
    std::iota(tmp, tmp + size, from);

    for (int i = 0; i < N; ++i) {
        arr[i] = tmp[(seed ^ i) % size];
    }

    delete[] tmp;
    return arr;
}

int main() {
    static_assert(generatePseudoRandom<int, 10>(10, 20).size() == 10);
}
Will print: 18 19 16 17 14 15 12 13 10 11
xor    eax,eax
ret    
cs nop WORD PTR [rax+rax*1+0x0]
nop    DWORD PTR [rax]
___

C++20 constexpr - allocation on heap (1)

If we forget to add delete[], compiler will detect it!

template <typename T, size_t N>
constexpr std::array<T, N> generatePseudoRandom(int from, int to) {
    std::array<T, N> arr;
    const int seed = ((N << 1) ^ (from << to) ^ to) ^ ((from + to) ^ (N << 3));
    const size_t size = to - from;
    auto tmp = new int[size];
    std::iota(tmp, tmp + size, from);

    for (int i = 0; i < N; ++i) {
        arr[i] = tmp[(seed ^ i) % size];
    }

    //delete[] tmp;
    return arr;
}

int main() {
    static_assert(generatePseudoRandom<int, 10>(10, 20).size() == 10);
}
<source>: In function 'int main()':
<source>:22:64: error: non-constant condition for static assertion
   22 |     static_assert(generatePseudoRandom<int, 10>(10, 20).size() == 10);
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
<source>:10:16: error: '(generatePseudoRandom<int, 10>(10, 20).std::array<int, 10>::size() == 10)' is not a constant expression because allocated storage has not been deallocated
   10 |     auto tmp = new int[size];
      |                ^~~~~~~~~~~~~
Execution build compiler returned: 1

contexpr-if (1)

  • Who uses SFINAE?
  • Who likes SFINAE?
namespace {
constexpr double epsilon = 0.000001;
}

template <class T> 
constexpr std::enable_if_t<std::is_floating_point_v<T>, bool> 
equal(T lhs, T rhs) {
   return std::abs(lhs - rhs) < epsilon;
}

template <class T>
constexpr std::enable_if_t<!std::is_floating_point_v<T>, bool> 
equal(T lhs, T rhs) {
   return lhs == rhs;
}

int main() {
    static_assert(equal(10, 10) == true);
    static_assert(equal(10.123f, 10.123f) == true);
    static_assert(equal(10.45678, 10.45678) == true);
}
xor    eax,eax
ret    
cs nop WORD PTR [rax+rax*1+0x0]
nop    DWORD PTR [rax]

contexpr-if (2)

template <typename T>
constexpr bool equal(T lhs, T rhs) {
    constexpr double epsilon = 0.000001;

    if constexpr (std::is_floating_point_v<T>) {
        return std::abs(lhs - rhs) < epsilon;
    } else {
        return lhs == rhs;
    }
}

int main() {
    static_assert(equal(10, 10) == true);
    static_assert(equal(10.123f, 10.123f) == true);
    static_assert(equal(10.45678, 10.45678) == true);
}
xor    eax,eax
ret    
cs nop WORD PTR [rax+rax*1+0x0]
nop    DWORD PTR [rax]

C++20 concept

template <typename T>
requires std::is_floating_point_v<T>
constexpr bool equal(T lhs, T rhs) {
    constexpr double epsilon = 0.000001;

    return std::abs(lhs - rhs) < epsilon;
}

// Work only, when both types are equal!
constexpr bool equal(auto lhs, auto rhs) {
    return lhs == rhs;
}

int main() {
    static_assert(equal(10, 10) == true);
    static_assert(equal(10.123f, 10.123f) == true);
    static_assert(equal(10.45678, 10.45678) == true);
}
xor    eax,eax
ret    
cs nop WORD PTR [rax+rax*1+0x0]
nop    DWORD PTR [rax]