Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cpp23-features

Доклад на тему "C++ 23 features"

Какие фичи мы затронем?

  • new ranges algorithms (python)
  • std::unreachable
  • multidimesional operator[]
  • mdspan
  • print
  • std::expected
  • deducing this

New ranges algorithms (Python in C++)

  1. std::views::join_with (Tralalero Tralala) A range adaptor that represents view consisting of the sequence obtained from flattening a view of ranges, with every element of the delimiter inserted in between elements of the view. The delimiter can be a single element or a view of elements.
#include <iostream>
#include <ranges>
#include <string_view>
#include <vector>
 
int main()
{
    using namespace std::literals;
 
    std::vector v{"This"sv, "is"sv, "a"sv, "test."sv};
    auto joined = v | std::views::join_with(' ');
 
    for (auto c : joined)
        std::cout << c;
    std::cout << '\n';
}
  1. std::views::repeat A range factory that generates a sequence of elements by repeatedly producing the same value. Can be either bounded or unbounded (infinite).
#include <iostream>
#include <ranges>
#include <string_view>
using namespace std::literals;
 
int main()
{
    // bounded overload
    for (auto s : std::views::repeat("C++"sv, 3))
        std::cout << s << ' ';
    std::cout << '\n';
 
    // unbounded overload
    for (auto s : std::views::repeat("I know that you know that"sv)
                | std::views::take(3))
        std::cout << s << ' ';
    std::cout << "...\n";
}
  1. std::views::slide slide_view is a range adaptor that takes a view and a number n and produces a view whose mth element (a "window") is a view over [m, m + n - 1] elements of the original view. Let s be the size of the original view. Then the size of produced view is: • s -n + n if s >= n, • 0 otherwise, and the resulting view is empty.
#include <algorithm>
#include <initializer_list>
#include <iostream>
#include <ranges>
 
auto print_subrange = [](std::ranges::viewable_range auto&& r)
{
    std::cout << '[';
    for (char space[]{0,0}; auto elem : r)
        std::cout << space << elem, *space = ' ';
    std::cout << "] ";
};
 
int main()
{
    const auto v = {1, 2, 3, 4, 5, 6};
 
    std::cout << "All sliding windows of width:\n";
    for (const unsigned width : std::views::iota(1U, 1U + v.size()))
    {
        auto const windows = v | std::views::slide(width);
        std::cout << "W = " << width << ": ";
        std::ranges::for_each(windows, print_subrange);
        std::cout << '\n';
    }
}
  1. std::views::stride stride_view is a range adaptor that takes a view and a number n and produces a view, that consists of elemer of the original view by advancing over n elements at a time. This means that each mth element of the produced view is (n * i)th element of the original view, for some non-negative index i. The elements of the original view whose "index" is not a multiple of n, are not present in the produced view. Let S be the size of the original view. Then the size of produced view is: • (S / n) + (5 % n ? 1 : 0), if S >= n; otherwise, • 1, if s > 0; otherwise, • 0 , and the resulting view is empty.
#include <algorithm>
#include <iostream>
#include <ranges>
#include <string_view>
using namespace std::literals;
 
void print(std::ranges::viewable_range auto&& v, std::string_view separator = " ")
{
    for (auto const& x : v)
        std::cout << x << separator;
    std::cout << '\n';
}
 
int main()
{
    print(std::views::iota(1, 13) | std::views::stride(3));
    print(std::views::iota(1, 13) | std::views::stride(3) | std::views::reverse);
    print(std::views::iota(1, 13) | std::views::reverse | std::views::stride(3));
 
    print("0x0!133713337*x//42/A$@"sv | std::views::stride(0B11) |
          std::views::transform([](char O) -> char { return 0100 | O; }),
          "");
}
  1. std::views::zip zip_view is a range adaptor that takes one or more views, and produces a view whose ith element is a tuple-like value consisting of the ith elements of all views. The size of produced view is the minimum of sizes of all adapted views
#include <array>
#include <iostream>
#include <list>
#include <ranges>
#include <string>
#include <tuple>
#include <vector>
 
void print(auto const rem, auto const& range)
{
    for (std::cout << rem; auto const& elem : range)
        std::cout << elem << ' ';
    std::cout << '\n';
}
 
int main()
{
    auto x = std::vector{1, 2, 3, 4};
    auto y = std::list<std::string>{"α", "β", "γ", "δ", "ε"};
    auto z = std::array{'A', 'B', 'C', 'D', 'E', 'F'};
 
    print("Source views:", "");
    print("x: ", x);
    print("y: ", y);
    print("z: ", z);
 
    print("\nzip(x,y,z):", "");
 
    for (std::tuple<int&, std::string&, char&> elem : std::views::zip(x, y, z))
    {
        std::cout << std::get<0>(elem) << ' '
                  << std::get<1>(elem) << ' '
                  << std::get<2>(elem) << '\n';
 
        std::get<char&>(elem) += ('a' - 'A'); // modifies the element of z
    }
 
    print("\nAfter modification, z: ", z);
}
  1. std::ranges::contains, std::ranges::contains_subrange
#include <algorithm>
#include <array>
#include <complex>
 
namespace ranges = std::ranges;
 
int main()
{
    constexpr auto haystack = std::array{3, 1, 4, 1, 5};
    constexpr auto needle = std::array{1, 4, 1};
    constexpr auto bodkin = std::array{2, 5, 2};
 
    static_assert
    (
        ranges::contains(haystack, 4) &&
       !ranges::contains(haystack, 6) &&
        ranges::contains_subrange(haystack, needle) &&
       !ranges::contains_subrange(haystack, bodkin)
    );
 
    constexpr std::array<std::complex<double>, 3> nums{{{1, 2}, {3, 4}, {5, 6}}};
}
  1. std::ranges::starts_with Checks whether the second range matches the prefix of the first range.
#include <algorithm>
#include <iostream>
#include <ranges>
#include <string_view>
 
int main()
{
    using namespace std::literals;
 
    constexpr auto ascii_upper = [](char8_t c)
    {
        return u8'a' <= c && c <= u8'z' ? static_cast<char8_t>(c + u8'A' - u8'a') : c;
    };
 
    constexpr auto cmp_ignore_case = [=](char8_t x, char8_t y)
    {
        return ascii_upper(x) == ascii_upper(y);
    };
 
    static_assert(std::ranges::starts_with("const_cast", "const"sv));
    static_assert(std::ranges::starts_with("constexpr", "const"sv));
    static_assert(!std::ranges::starts_with("volatile", "const"sv));
 
    constexpr static auto v = { 1, 3, 5, 7, 9 };
    constexpr auto odd = [](int x) { return x % 2; };
    static_assert(std::ranges::starts_with(v, std::views::iota(1)
                                            | std::views::filter(odd)
                                            | std::views::take(3)));
}
#include <algorithm>
#include <iostream>
#include <ranges>
#include <string_view>

int main()
{
    using namespace std::literals;
 
    constexpr auto ascii_upper = [](char8_t c)
    {
        return u8'a' <= c && c <= u8'z' ? static_cast<char8_t>(c + u8'A' - u8'a') : c;
    };
 
    constexpr auto cmp_ignore_case = [=](char8_t x, char8_t y)
    {
        return ascii_upper(x) == ascii_upper(y);
    };

    std::cout << std::boolalpha
              << std::ranges::starts_with(u8"Constantinopolis", u8"constant"sv,
                                          {}, ascii_upper, ascii_upper) << ' '
              << std::ranges::starts_with(u8"Istanbul", u8"constant"sv,
                                          {}, ascii_upper, ascii_upper) << ' '
              << std::ranges::starts_with(u8"Metropolis", u8"metro"sv,
                                          cmp_ignore_case) << ' '
              << std::ranges::starts_with(u8"Acropolis", u8"metro"sv,
                                          cmp_ignore_case) << '\n';
}
  1. std::views::chunk, std::views::chunk_by chunk_by_view is a range adaptor that takes a view and an invocable object pred (the binary predicate), and produces a view of subranges (chunks), by splitting the underlying view between each pair of adjacent elements for which pred returns false. The first element of each such pair belongs to the previous chunk, and the second element belongs to the next chunk.
#include <algorithm>
#include <initializer_list>
#include <iostream>
#include <ranges>
 
auto print_subrange = [](std::ranges::viewable_range auto&& r)
{
    std::cout << '[';
    for (int pos{}; auto elem : r)
        std::cout << (pos++ ? " " : "") << elem;
    std::cout << "] ";
};
 
int main()
{
    const auto v = {1, 2, 3, 4, 5, 6};
 
    for (const unsigned width : std::views::iota(1U, 2U + v.size()))
    {
        auto const chunks = v | std::views::chunk(width);
        std::cout << "chunk(" << width << "): ";
        std::ranges::for_each(chunks, print_subrange);
        std::cout << '\n';
    }
}
#include <functional>
#include <iostream>
#include <ranges>
#include <string_view>
 
void print_chunks(auto view, std::string_view separator = ", ")
{
    for (auto const subrange : view)
    {
        std::cout << '[';
        for (std::string_view prefix; auto const& elem : subrange)
            std::cout << prefix << elem, prefix = separator;
        std::cout << "] ";
    }
    std::cout << '\n';
}
 
int main()
{
    std::initializer_list v1 = {1, 2, 3, 1, 2, 3, 3, 3, 1, 2, 3};
    auto fn1 = std::ranges::less{};
    auto view1 = v1 | std::views::chunk_by(fn1);
    print_chunks(view1);
 
    std::initializer_list v2 = {1, 2, 3, 4, 4, 0, 2, 3, 3, 3, 2, 1};
    auto fn2 = std::ranges::not_equal_to{};
    auto view2 = v2 | std::views::chunk_by(fn2);
    print_chunks(view2);
 
    std::string_view v3 = "__cpp_lib_ranges_chunk_by";
    auto fn3 = [](auto x, auto y) { return not(x == '_' or y == '_'); };
    auto view3 = v3 | std::views::chunk_by(fn3);
    print_chunks(view3, "");
 
    std::string_view v4 = "\u007a\u00df\u6c34\u{1f34c}"; // "zß水🍌"
    auto fn4 = [](auto, auto ß) { return 128 == ((128 + 64) & ß); };
    auto view4 = v4 | std::views::chunk_by(fn4);
    print_chunks(view4, "");
}
  1. std::views::cartesian_product
#include <array>
#include <iostream>
#include <list>
#include <ranges>
#include <string>
#include <vector>
 
void print(std::tuple<char const&, int const&, std::string const&> t, int pos)
{
    const auto& [a, b, c] = t;
    std::cout << '(' << a << ' ' << b << ' ' << c << ')' << (pos % 4 ? " " : "\n");
}
 
int main()
{
    const auto x = std::array{'A', 'B'};
    const auto y = std::vector{1, 2, 3};
    const auto z = std::list<std::string>{"α", "β", "γ", "δ"};
 
    for (int i{1}; auto const& tuple : std::views::cartesian_product(x, y, z))
        print(tuple, i++);
}

std::unreachable

Invokes undefined behavior at a given point. An implementation may use this to optimize impossible code branches away (typically, in optimized builds) or to trap them to prevent further execution (typically, in debug builds).

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>
 
struct Color { std::uint8_t r, g, b, a; };
 
// Assume that only restricted set of texture caps is supported.
void generate_texture(std::vector<Color>& tex, std::size_t xy)
{
    switch (xy)
    {
    case 128: [[fallthrough]];
    case 256: [[fallthrough]];
    case 512: /* ... */
        tex.clear();
        tex.resize(xy * xy, Color{0, 0, 0, 0});
        break;
    default:
        std::unreachable();
        std::cout << 5;
    }
}
 
int main()
{
    std::vector<Color> tex;
    generate_texture(tex, 128); // OK
    assert(tex.size() == 128 * 128);
    generate_texture(tex, 32);  // Results in undefined behavior
}

clang just skips code after std::unreachable(), g++ terminate

multidimesional operator[]

(until C++23) operator[] can only take one subscript. In order to provide multidimensional array access semantics, e.g. to implement a 3D array access a[i][j][k] = x;, operator[] has to return a reference to a 2D plane, which has to have its own operator[] which returns a reference to a 1D row, which has to have operator[] which returns a reference to the element. To avoid this complexity, some libraries opt for overloading operator() instead, so that 3D access expressions have the Fortran-like syntax a(i, j, k) = x;.

(since C++23) operator[] can take any number of subscripts. For example, an operator[] of a 3D array class declared as T& operator[](std::size_t x, std::size_t y, std::size_t z); can directly access the elements.

#include <array>
#include <cassert>
#include <iostream>
 
template<typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d
{
    std::array<T, X * Y * Z> m{};
 
    constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x) // C++23
    {
        assert(x < X and y < Y and z < Z);
        return m[z * Y * X + y * X + x];
    }
};
 
int main()
{
    Array3d<int, 4, 3, 2> v;
    v[3, 2, 1] = 42;
    std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}

mdspan

std:: mdspan is a multidimensional array view that maps a multidimensional index to an element of the array. The mapping and element access policies are configurable, and the underlying array need not be contiguous or even exist in memory at all

#include <cstddef>
#include <mdspan>
#include <print>
#include <vector>
 
int main()
{
    std::vector v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
 
    // View data as contiguous memory representing 2 rows of 6 ints each
    auto ms2 = std::mdspan(v.data(), 2, 6);
    // View the same data as a 3D array 2 x 3 x 2
    auto ms3 = std::mdspan(v.data(), 2, 3, 2);
 
    // Write data using 2D view
    for (std::size_t i = 0; i != ms2.extent(0); i++)
        for (std::size_t j = 0; j != ms2.extent(1); j++)
            ms2[i, j] = i * 1000 + j;
 
    // Read back using 3D view
    for (std::size_t i = 0; i != ms3.extent(0); i++)
    {
        std::println("slice @ i = {}", i);
        for (std::size_t j = 0; j != ms3.extent(1); j++)
        {
            for (std::size_t k = 0; k != ms3.extent(2); k++)
                std::print("{} ", ms3[i, j, k]);
            std::println("");
        }
    }
}

std::print, std::println

Format args according to the format string fmt, and print the result to an output stream.

#include <cstdio>
#include <filesystem>
#include <print>
 
int main()
{
    std::print("{2} {1}{0}!\n", 23, "C++", "Hello");  // overload (1)
 
    const auto tmp{std::filesystem::temp_directory_path() / "test.txt"};
    if (std::FILE* stream{std::fopen(tmp.c_str(), "w")})
    {
        std::print(stream, "File: {}", tmp.string()); // overload (2)
        std::fclose(stream);
    }
}

Format args according to the format string fmt with appended '\n' (which means that each output ends with a new-line), and print the result to a stream.

#include <print>
 
int main()
{
    // Each call to std::println ends with new-line
    std::println("Please"); // overload (1)
    std::println("enter"); // (1)
 
    std::print("pass");
    std::print("word");
 
    std::println(); // (3); valid since C++26; same effect as std::print("\n"); 
}

std::expected

Мотивация (tbc)

The class template std::expected provides a way to represent either of two values: an expected value of type T, or an unexpected value of type E. expected is never valueless.

  1. The main template. Contains the expected or unexpected value within its own storage, which is nested within the expected object.
  2. The void partial specialization. Represents an expected void value or contains an unexpected value. If it contains an unexpected value, it is nested within the expected object. A program is ill-formed if it instantiates an expected with a reference type, a function type, or a specialization of std::unexpected. In addition, T must not be std::in_place_t or std::unexpect_t.
#include <cmath>
#include <expected>
#include <iomanip>
#include <iostream>
#include <string_view>
 
enum class parse_error
{
    invalid_input,
    overflow
};
 
auto parse_number(std::string_view& str) -> std::expected<double, parse_error>
{
    const char* begin = str.data();
    char* end;
    double retval = std::strtod(begin, &end);
 
    if (begin == end)
        return std::unexpected(parse_error::invalid_input);
    else if (std::isinf(retval))
        return std::unexpected(parse_error::overflow);
 
    str.remove_prefix(end - begin);
    return retval;
}
 
int main()
{
    auto process = [](std::string_view str)
    {
        std::cout << "str: " << std::quoted(str) << ", ";
        if (const auto num = parse_number(str); num.has_value())
            std::cout << "value: " << *num << '\n';
        else if (num.error() == parse_error::invalid_input)
            std::cout << "error: invalid input\n";
        else if (num.error() == parse_error::overflow)
            std::cout << "error: overflow\n";
        else
            std::cout << "unexpected!\n"; // or invoke std::unreachable();
    };
 
    for (auto src : {"42", "42abc", "meow", "inf"})
        process(src);
}

deducing this

Начиная с C++23, появилась возможность явно указывать параметр this в методах класса. Это позволяет:

  1. Упростить перегрузки методов
  2. Избежать дублирования кода
  3. Более эффективно работать с производными типами
#include <iostream>
#include <string>

struct Greeter {
    std::string name;

    void hello() const {
        std::cout << "Hello, " << name << "!\n";
    }
    
    template <typename Self>
    void greet(this Self&& self) {
        std::cout << "Greetings, " << self.name << "!\n";
    }

    // template <typename Self>
    // void greet(this Self&& self) {
    //     std::cout << "Greetings, " << name << "!\n";
    // }
    // syntax.cpp:13:39: error: invalid use of member 'name' in explicit object member function
    // 13 |         std::cout << "Greetings, " << name << "!\n";
    //    |                                       ^~~~
    //    |                                       self.
};

int main() {
    Greeter g{"World"};
    g.hello();
    g.greet();
}
#include <vector>
#include <iostream>
#include <algorithm>
#include <typeinfo>
#include <utility>
#include <type_traits>

class MyVector {
    std::vector<int> data;
public:
    // До C++23 требовалось 4 перегрузки
    template <typename Self>
    decltype(auto) operator[](this Self&& self, size_t index) {
        return std::forward<Self>(self).data[index];
    }
};
  
struct DerivedVec : MyVector {};


int main() {
    MyVector vec;

    vec[1]; // lvalue non-const

    const MyVector constVec = vec;
    constVec[1]; // const lvalue

    std::move(vec)[1]; // rvalue
    std::move(constVec)[1]; // const rvalue

    DerivedVec d;
    d[1]; // derived call
}

About

Доклад на тему "C++ 23 features"

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages