Jun 13, 2022

[C++][C++20] compile time heap allocate

Reference:

Compile-time functions can allocate memory provided the memory is also released at compile time.

For this reason, you can now use strings or vectors at compile time. 
However, you cannot use the compile-time created strings or vectors at runtime because memory allocated at compile time has to be released at compile time.
#include <vector>
#include <ranges>
#include <algorithm>
#include <numeric>

template<std::ranges::input_range T>
constexpr auto modifiedAvg(const T& rg) {
    using elemType = std::ranges::range_value_t<T>;
    // initialize compile-time vector with passed elements:
    std::vector<elemType> v{std::ranges::begin(rg),
    std::ranges::end(rg)};
    // perform several modifications:
    v.push_back(elemType{});
    std::ranges::sort(v);
    auto newEnd = std::unique(v.begin(), v.end());

    // return average of modified vector:
    auto sum = std::accumulate(v.begin(), newEnd, elemType{});
    return sum / static_cast<double>(v.size());
}

// 注意,要用constexpr不然modifiedAvg為runtime.
constexpr auto avg = modifiedAvg(orig);


// use concept
// initialize compile-time vector with passed elements
template<std::ranges::input_range T>
consteval auto modifiedAvg(T rg) {
    using elemType = std::ranges::range_value_t<T>;
    std::vector<elemType> v{std::ranges::begin(rg), std::ranges::end(rg)};
}

However, note that we still cannot declare and initialize a vector at compile time that is usable at runtime:
int main() {
    constexpr std::vector orig{0, 8, 15, 132, 4, 77}; // ERROR
}

For the same reason, a compile-time function can only return a vector to the caller when the return value is used at compile time:
#include <vector>

constexpr auto returnVector() {
    std::vector<int> v{0, 8, 15};
    v.push_back(42);
    return v;
}

constexpr auto returnVectorSize() {
    constexpr auto coll = returnVector();
    return coll.size();
}

int main() {
    // constexpr auto coll = returnVector(); // ERROR
    constexpr auto tmp = returnVectorSize();
}
#include <vector>
#include <ranges>
#include <algorithm>
#include <array>

template<std::ranges::input_range T>
consteval auto mergeValuesSz(T rg, auto... vals) {
// create compile-time vector:
std::vector<std::ranges::range_value_t<T>> v{
    std::ranges::begin(rg), std::ranges::end(rg)};

    (... , v.push_back(vals)); // and merge passed values
    std::ranges::sort(v);

    constexpr auto maxSz = rg.size() + sizeof...(vals);
    std::array<std::ranges::range_value_t<T>, maxSz> arr{};
    auto res = std::ranges::unique_copy(v, arr.begin());

    return std::pair{arr, res.out - arr.begin()};
}

Using Strings at Compile Time:
Rule of thumb: cannot use a compile-time string at runtime.
String SSO implement also take into account. (https://godbolt.org/z/eTYfcfMc5)



constexpr Language Extensions

Since C++20, the following language features are possible to be used in compile time functions (whether declared with constexpr or consteval):
  • You can now use heap memory at compile time.
  • Runtime polymorphism is supported:
    • You can now use virtual functions.
    • You can now use dynamic_cast.
    • You can now use typeid.
  • You can have try-catch blocks now (but you are still not allowed to throw).
  • You can now change the active member of a union.
  • Note that you are still not allowed to use static in constexpr or consteval functions.


lamdba

template<typename... Args>
void foo(Args... args) {
    // OK since C++20
    auto l4 = [...args = std::move(args)] {
        bar(args...); // OK
    };
}

template<typename... Args>
void foo(Args... args) {
    auto l4 = [&...fooArgs = args] {
        bar(fooArgs...); // OK
    };
}



new type:
char8_t
std::u8string
std::u8string_view

char8_t c = u8'@';      // character with UTF-8 encoding for character @
const char8_t* s = u8"K\u00F6ln";       // character sequence with UTF-8 encoding for Köln



#include <iostream>
#include <cmath>
#include <thread>
#include <syncstream>

void squareRoots(int num) {
    for (int i = 0; i < num ; ++i) {
        std::osyncstream coutSync{std::cout};
        coutSync << "squareroot of " << i << " is "
            << std::sqrt(i) << '\n';
    }
}

int main() {
    std::jthread t1(squareRoots, 5);
    std::jthread t2(squareRoots, 5);
    std::jthread t3(squareRoots, 5);
}


For writing to file:
#include <fstream>
#include <cmath>
#include <thread>
#include <syncstream>

void squareRoots(std::ostream& strm, int num) {

    std::osyncstream syncStrm{strm};

    for (int i = 0; i < num ; ++i) {
        syncStrm << "squareroot of " << i << " is "
            << std::sqrt(i) << '\n' << std::flush_emit;
    }
}


int main() {
    std::ofstream fs{"tmp.out"};
    std::jthread t1(squareRoots, std::ref(fs), 5);
    std::jthread t2(squareRoots, std::ref(fs), 5);
    std::jthread t3(squareRoots, std::ref(fs), 5);
}
or:
#include <fstream>
#include <cmath>
#include <thread>
#include <syncstream>

void squareRoots(std::ostream& strm, int num) {
    for (int i = 0; i < num ; ++i) {
        strm << "squareroot of " << i << " is "
        << std::sqrt(i) << '\n' << std::flush_emit;
    }
}

int main() {
    std::ofstream fs{"tmp.out"};
    std::osyncstream syncStrm1{fs};
    std::jthread t1(squareRoots, std::ref(syncStrm1), 5);

    std::osyncstream syncStrm2{fs};
    std::jthread t2(squareRoots, std::ref(syncStrm2), 5);

    std::osyncstream syncStrm3{fs};
    std::jthread t3(squareRoots, std::ref(syncStrm3), 5);
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.