1082 lines
40 KiB
Markdown
1082 lines
40 KiB
Markdown
## Small features
|
||
<!-- .slide: data-background="#ccc" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## operator<=>
|
||
|
||
Since C++20 we don't need to define all comparison operators for custom structures. Instead of `==`, `!=`, `>`, `<`, `<=`, `>=` we can simply write one universal comparator `<=>`.
|
||
|
||
```C++
|
||
struct Student {
|
||
auto operator<=>(const Student& other) const = default;
|
||
|
||
std::string name_;
|
||
double average_;
|
||
int index_;
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .average_ = 4.67, .index_ = 456123};
|
||
Student student2{.name_ = "Michał", .average_ = 4.57, .index_ = 123456};
|
||
Student student3{.name_ = "Marcelina", .average_ = 4.67, .index_ = 321456};
|
||
Student student4{.name_ = "Mirosław", .average_ = 4.47, .index_ = 135246};
|
||
|
||
std::cout << "student1 < student2 ? " << (student1 < student2) << '\n'; // True
|
||
std::cout << "student1 < student3 ? " << (student1 < student3) << '\n'; // False
|
||
std::cout << "student1 < student4 ? " << (student1 < student4) << '\n'; // True
|
||
std::cout << "student2 < student3 ? " << (student2 < student3) << '\n'; // False
|
||
std::cout << "student2 < student4 ? " << (student2 < student4) << '\n'; // True
|
||
std::cout << "student3 < student4 ? " << (student3 < student4) << '\n'; // True
|
||
std::cout << "student1 == student1 ? " << (student1 == student1) << '\n'; // True
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.80em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## How it works?
|
||
|
||
First, we need to understand how the compiler treats the structure `Student`. This structure has 3 fields: `name`, `average`, and `index`. During the comparison, the first field is to compare if both values are the same, if true we reach a second one, and so on. In other words, first different fields decide which structure is lower.
|
||
|
||
```C++
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .average_ = 4.67, .index_ = 456123};
|
||
Student student2{.name_ = "Mateusz", .average_ = 4.67, .index_ = 123456};
|
||
Student student3{.name_ = "Mateusz", .average_ = 4.57, .index_ = 321456};
|
||
Student student4{.name_ = "Mateusz", .average_ = 4.57, .index_ = 135246};
|
||
|
||
std::cout << "student1 > student2 ? " << (student1 > student2) << '\n'; // T (higher index)
|
||
std::cout << "student1 > student3 ? " << (student1 > student3) << '\n'; // T (higher average)
|
||
std::cout << "student1 > student4 ? " << (student1 > student4) << '\n'; // T (higher average)
|
||
std::cout << "student2 > student3 ? " << (student2 > student3) << '\n'; // T (higher average)
|
||
std::cout << "student2 > student4 ? " << (student2 > student4) << '\n'; // T (higher average)
|
||
std::cout << "student3 > student4 ? " << (student3 > student4) << '\n'; // T (higher index)
|
||
std::cout << "student1 == student1 ? " << (student1 == student1) << '\n'; // T (the same person)
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.80em" -->
|
||
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## But I don't want to compare some fields...
|
||
|
||
When you don't want to compare one or more fields, you need to write your own comparator.
|
||
|
||
```C++
|
||
struct Student {
|
||
std::partial_ordering operator<=>(const Student& other) const {
|
||
if (const auto res = average_ <=> other.average_; res != 0) {
|
||
return res;
|
||
}
|
||
return name_ <=> other.name_;
|
||
}
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .average_ = 4.67, .index_ = 456123};
|
||
Student student2{.name_ = "Mateusz", .average_ = 4.67, .index_ = 123456};
|
||
Student student3{.name_ = "Mateusz", .average_ = 4.57, .index_ = 321456};
|
||
|
||
std::cout << "student1 > student2 ? " << (student1 > student2) << '\n'; // T (higher index)
|
||
std::cout << "student1 < student3 ? " << (student1 < student3) << '\n'; // F (lower average)
|
||
// This line do not compile :(
|
||
std::cout << "student1 == student1 ? " << (student1 == student1) << '\n'; // T (the same person)
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
But why do we wrote`std::partial_ordering` instead of `auto`? It's because we compare two floating point values. I know that earlier we wrote `auto operator<=>` and it's worked, but this was fully generated by the compiler. If we are writing it manually and we need to specify what result we return.
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.74em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Types of ordering
|
||
|
||
* <!-- .element: class="fragment fade-in" --> <code>strong_ordering</code>: a total ordering, where equality implies substitutability (that is (a <=> b) == strong_ordering::equal implies that for reasonable functions f, f(a) == f(b). “Reasonable” is deliberately underspecified – but shouldn’t include functions that return the address of their arguments or do things like return the capacity() of a vector, etc. We want to only look at “salient” properties – itself very underspecified, but think of it as referring to the value of a type. The value of a vector is the elements it contains, not its address, etc.). The values are strong_ordering::greater, strong_ordering::equal, and strong_ordering::less.
|
||
* <!-- .element: class="fragment fade-in" --> <code>weak_ordering</code>: a total ordering, where equality actually only defines an equivalence class. The canonical example here is case-insensitive string comparison – where two objects might be weak_ordering::equivalent but not actually equal (hence the naming change to equivalent).
|
||
* <!-- .element: class="fragment fade-in" --> <code>partial_ordering</code>: a partial ordering. Here, in addition to the values greater, equivalent, and less (as with weak_ordering), we also get a new value: unordered. This gives us a way to represent partial orders in the type system: 1.f <=> NaN is partial_ordering::unordered.
|
||
<!-- .slide: style="font-size: 0.85em" -->
|
||
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Ok but what with operator==
|
||
|
||
beacuse we have custom `operator<=>`, which return `std::partial_ordering` we can't use `operator==`. We need to provide own `operator==`. And we can do this by default (compare all fileds) or manually (compare specific fileds).
|
||
|
||
```C++
|
||
struct Student {
|
||
std::partial_ordering operator<=>(const Student& other) const {
|
||
if (const auto res = average_ <=> other.average_; res != 0) {
|
||
return res;
|
||
}
|
||
return name_ <=> other.name_;
|
||
}
|
||
|
||
bool operator==(const Student& other) const {
|
||
return name_ == other.name_ && average_ == other.average_;
|
||
}
|
||
|
||
std::string name_;
|
||
double average_;
|
||
int index_;
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .average_ = 4.67, .index_ = 456123};
|
||
Student student2{.name_ = "Mateusz", .average_ = 4.67, .index_ = 123456};
|
||
|
||
std::cout << "student1 > student2 ? " << (student1 > student2) << '\n'; // T (higher index)
|
||
std::cout << "student1 == student1 ? " << (student1 == student1) << '\n'; // T (the same person)
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.74em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
We can also decide to use default `operator==` and this also work!
|
||
|
||
```C++
|
||
struct Student {
|
||
std::partial_ordering operator<=>(const Student& other) const {
|
||
if (const auto res = average_ <=> other.average_; res != 0) {
|
||
return res;
|
||
}
|
||
return name_ <=> other.name_;
|
||
}
|
||
|
||
bool operator==(const Student&) const = default;
|
||
|
||
std::string name_;
|
||
double average_;
|
||
int index_;
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .average_ = 4.67, .index_ = 456123};
|
||
Student student2{.name_ = "Mateusz", .average_ = 4.67, .index_ = 123456};
|
||
|
||
std::cout << "student1 > student2 ? " << (student1 > student2) << '\n'; // T (higher index)
|
||
std::cout << "student1 == student1 ? " << (student1 == student1) << '\n'; // T (the same person)
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.74em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## More about partial_ordering
|
||
|
||
To sum up, if we compare 2 floating point values, the result is `std::partial_ordering`. We can't use `operator==` in such a case. This makes sense, but when do we also need this type of order? Let's check the below example:
|
||
|
||
```C++
|
||
struct Student {
|
||
std::partial_ordering operator<=>(const Student& other) const {
|
||
if (!name_ || !other.name_) {
|
||
return std::partial_ordering::unordered;
|
||
}
|
||
if (const auto res = name_ <=> other.name_; res != 0) {
|
||
return res;
|
||
}
|
||
return index_ <=> other.index_;
|
||
}
|
||
bool operator==(const Student&) const = default;
|
||
|
||
std::optional<std::string> name_;
|
||
double average_;
|
||
int index_;
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .average_ = 4.67, .index_ = 456123};
|
||
Student student2{.name_ = std::nullopt, .average_ = 4.67, .index_ = 123456};
|
||
|
||
std::cout << "student1 > student2 ? " << (student1 > student2) << '\n'; // F (second student doesn't has name!)
|
||
std::cout << "student1 < student2 ? " << (student1 < student2) << '\n'; // F (second student doesn't has name!)
|
||
std::cout << "student1 != student2 ? " << (student1 != student2) << '\n'; // T (here we use operator== not <=>)
|
||
std::cout << "student1 == student2 ? " << (student1 == student2) << '\n'; // F (here we use operator== not <=>)
|
||
std::cout << "student2 == student2 ? " << (student2 == student2) << '\n'; // T (here we use operator== not <=>)
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.70em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Ok but how <=> exactly works?
|
||
|
||
Each type or ordering could represent one of those 3 values (partial could also has `unordered`):
|
||
|
||
* std::strong_ordering::less / weak_ordering::less / std::partial_ordering::less
|
||
* std::strong_ordering::equal / weak_ordering::equivalent / std::partial_ordering::equivalent
|
||
* std::strong_ordering::greater / weak_ordering::greater / std::partial_ordering::greater
|
||
|
||
We can simply compare them:
|
||
<!-- .element: class="fragment fade-in" -->
|
||
```C++
|
||
int val1(1234);
|
||
int val2(12345);
|
||
auto res = val1 <=> val2;
|
||
if (res < 0)
|
||
std::cout << "val1 < val2" << std::endl;
|
||
else if (res == 0)
|
||
std::cout << "val1 == val2" << std::endl;
|
||
else if (res > 0)
|
||
std::cout << "val1 > val2" << std::endl;
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
This is implicity generated by compiler. For instance `a > b` is converted to `(a <=> b) > 0`
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.85em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Finally, something about weak ordering
|
||
|
||
Weak ordering is returned when does not imply substitutability: if a is equivalent to b, f(a) may not be equivalent to f(b), where f denotes a function that reads only comparison-salient state that is accessible via the argument's public const members. In other words, equivalent values may be distinguishable.
|
||
|
||
```C++
|
||
struct Student {
|
||
int score() const {
|
||
return std::accumulate(cbegin(grades_), cend(grades_), 0, [](const auto& sum, const auto& pair) { return sum + pair.second; });
|
||
}
|
||
|
||
std::weak_ordering operator<=>(const Student& other) const {
|
||
if (const auto res = this->score() <=> other.score(); res != 0) {
|
||
return res;
|
||
}
|
||
return name_ <=> other.name_;
|
||
}
|
||
|
||
bool operator==(const Student&) const = default;
|
||
|
||
std::string name_;
|
||
std::multimap<std::string, int> grades_;
|
||
int index_;
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .grades_ = {{"Math", 5},{"Bio", 4}}, .index_ = 456123};
|
||
Student student2{.name_ = "Mateusz", .grades_ = {{"Math", 5},{"Eng", 4}}, .index_ = 123456};
|
||
Student student3{.name_ = "Mateusz", .grades_ = {{"Math", 4},{"Phys", 3}}, .index_ = 123456};
|
||
|
||
std::cout << "student1 > student2 ? " << (student1 > student2) << '\n'; // F
|
||
std::cout << "student1 < student2 ? " << (student1 < student2) << '\n'; // F
|
||
std::cout << "student1 != student2 ? " << (student1 != student2) << '\n'; // T (use operator == here)
|
||
std::cout << "student1 == student2 ? " << (student1 == student2) << '\n'; // F (use operator == here)
|
||
std::cout << "student1 > student3 ? " << (student1 > student3) << '\n'; // T (higher grades)
|
||
std::cout << "student1 < student3 ? " << (student1 < student3) << '\n'; // F
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Implicit conversion
|
||
|
||
`std::strong_ordering` can be implicitly converted to `std::weak_ordering` and `std::weak_ordering` can be implicitly converted to `std::partial_ordering`.
|
||
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## designated initializers
|
||
|
||
This is a small feature that you saw a few times in action during these lectures:
|
||
|
||
```C++
|
||
struct Student {
|
||
std::string name_;
|
||
std::multimap<std::string, int> grades_;
|
||
int index_;
|
||
};
|
||
|
||
int main() {
|
||
Student student1{.name_ = "Mateusz", .grades_ = {{"Math", 5},{"Bio", 4}}, .index_ = 456123};
|
||
Student student2{.name_ = "Mateusz", .grades_ = {{"Math", 5},{"Eng", 4}}, .index_ = 123456};
|
||
Student student3{.name_ = "Mateusz", .grades_ = {{"Math", 4},{"Phys", 3}}, .index_ = 123456};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.85em" -->
|
||
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
Every structure or class with public fields could now be initialized by using the name of a field. This makes code more readable and you don't need to switch a few times between headers and source code to check which field means what.
|
||
|
||
```C++
|
||
struct StreamInfo {
|
||
std::string sourceAddress_;
|
||
std::string destinationAddress_;
|
||
uint16_t sourcePort_;
|
||
uint16_t destinationPort_;
|
||
uint16_t vlan_;
|
||
};
|
||
|
||
struct Streamer {
|
||
Streamer(uint16_t sourcePort): sourcePort_(sourcePort) {}
|
||
|
||
uint16_t getSourcePort() const { return sourcePort_; }
|
||
private:
|
||
uint16_t sourcePort_;
|
||
};
|
||
|
||
int main() {
|
||
std::map<std::string, std::string> ipTables;
|
||
Streamer streamer(6523);
|
||
ipTables.emplace("192.168.0.15", "192.168.0.35");
|
||
ipTables.emplace("192.168.0.16", "192.168.0.42");
|
||
|
||
StreamInfo info {
|
||
.sourceAddress_ = "192.168.0.15",
|
||
.destinationAddress_ = ipTables["192.168.0.15"],
|
||
.sourcePort_ = streamer.getSourcePort(),
|
||
.destinationPort_ = 9998,
|
||
.vlan_ = [](){ return 123; }()
|
||
};
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.64em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Atributes
|
||
|
||
Since C++20 we got 4 more atributes
|
||
|
||
* <!-- .element: class="fragment fade-in" --> <b>[[nodiscard("reason")]]</b> - encourages the compiler to issue a warning if the return value is discarded
|
||
* <!-- .element: class="fragment fade-in" --> <b>[[likely]]</b> and <b>[[unlikely]]</b> - indicates that the compiler should optimize for the case where a path of execution through a statement is more or less likely than any other path of execution
|
||
* <!-- .element: class="fragment fade-in" --> <b>[[no_unique_address]]</b> - indicates that a non-static data member need not have an address distinct from all other non-static data members of its class
|
||
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## likely and unlikely
|
||
|
||
Everyone knows well this `if-else` statements:
|
||
|
||
```C++
|
||
if (!vec.empty()) { return vec.front(); }
|
||
|
||
if (divider != 0) {
|
||
return num / divider;
|
||
} else {
|
||
return std::nan("nan");
|
||
}
|
||
|
||
if (ptr) {
|
||
return ptr->doSth();
|
||
}
|
||
|
||
if (name_ == other.name_) {
|
||
return index < other.index_;
|
||
} else {
|
||
return name_ < other.name_
|
||
}
|
||
```
|
||
* <!-- .element: class="fragment fade-in" --> <b>What is common for all of them?</b>
|
||
<!-- .slide: style="font-size: 0.94em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
Let's help compiler!
|
||
|
||
```C++
|
||
constexpr double pow(double x, int n) noexcept {
|
||
if (n > 0) [[likely]] { return x * pow(x, n - 1); }
|
||
else [[unlikely]] { return 1; }
|
||
}
|
||
|
||
constexpr double pow2(double x, int n) noexcept {
|
||
if (n > 0) { return x * pow(x, n - 1); }
|
||
else { return 1; }
|
||
}
|
||
|
||
int main() {
|
||
auto benchmark = [](auto fun) {
|
||
const auto start = std::chrono::high_resolution_clock::now();
|
||
fun();
|
||
const auto diff = std::chrono::high_resolution_clock::now() - start;
|
||
std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() << " ns\n";
|
||
};
|
||
|
||
std::vector<double> vec(1'000'000);
|
||
std::iota(begin(vec), end(vec), 1);
|
||
benchmark([&]() { for (auto el : vec) { pow(el, 13); } });
|
||
benchmark([&]() { for (auto el : vec) { pow2(el, 13); } });
|
||
// Time: 59839100 ns
|
||
// Time: 79786800 ns
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.76em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
There is more!
|
||
|
||
```C++
|
||
int Nwd(int a, int b) {
|
||
while (b != 0) [[likely]] { a = std::exchange(b, a % b); }
|
||
return a;
|
||
}
|
||
|
||
int Nwd2(int a, int b) {
|
||
while (b != 0) { a = std::exchange(b, a % b); }
|
||
return a;
|
||
}
|
||
|
||
int main() {
|
||
auto benchmark = [](auto fun) {
|
||
const auto start = std::chrono::high_resolution_clock::now();
|
||
fun();
|
||
const auto diff = std::chrono::high_resolution_clock::now() - start;
|
||
std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() << " ns\n";
|
||
};
|
||
|
||
std::vector<double> vec(1'000'000);
|
||
std::iota(begin(vec), end(vec), 1);
|
||
benchmark([&](){ for (auto el : vec) { Nwd(1'000'000, el); } });
|
||
benchmark([&](){ for (auto el : vec) { Nwd2(1'000'000, el); } });
|
||
// Time: 140909100 ns
|
||
// Time: 182572100 ns
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.76em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Ok ok, but what happens when I already use optimization flag?
|
||
|
||
```C++
|
||
int main() {
|
||
auto benchmark = [](auto fun) {
|
||
const auto start = std::chrono::high_resolution_clock::now();
|
||
fun();
|
||
const auto diff = std::chrono::high_resolution_clock::now() - start;
|
||
std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() << " ns\n";
|
||
};
|
||
|
||
std::vector<double> vec(1'000'000);
|
||
std::iota(begin(vec), end(vec), 1);
|
||
int res = 0;
|
||
benchmark([&](){ for (auto el : vec) { res += Nwd(1'000'000, el); } }); // Time: 88765100 ns
|
||
benchmark([&](){ for (auto el : vec) { res += Nwd2(1'000'000, el); } }); // Time: 82797600 ns
|
||
benchmark([&]() { for (auto el : vec) { res += pow(el, 13); } }); // Time: 6002300 ns
|
||
benchmark([&]() { for (auto el : vec) { res += pow2(el, 13); } }); // Time: 8012500 ns
|
||
|
||
return res;
|
||
}
|
||
```
|
||
|
||
<b>Wait what?!</b>. When we use <b>-O3</b> optimization flag we got worse output for <b>[[likely]]</b>. It depends on various things, sometimes code run faster sometimes not, but generally, you can't charm the compiler with optimization, but sometimes you can help :)
|
||
<!-- .slide: style="font-size: 0.76em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
Use it when it is worth it, and the compiler could have a problem with manual optimization. For instance in 99.99% during response validation, an error occurs when there is no `result` filed, other cases happen mainly when someone implements wrong behavior and this is eliminated during tests. Ofc you still can always write more predictable if statement at the beginning :) And this is even better option in my opinion.
|
||
|
||
```C++
|
||
bool validate(const std::string& str) {
|
||
constexpr char kResult[] = "result:";
|
||
|
||
if (str.empty()) [[unlikely]] {
|
||
return false;
|
||
} else if (str.size() > 30) [[unlikely]] {
|
||
return false;
|
||
} else if (str.front() != '{') [[unlikely]] {
|
||
return false;
|
||
} else if (str.back() != '}') [[unlikely]] {
|
||
return false;
|
||
} else if (std::all_of(std::cbegin(str), std::cend(str), [](const char c) { return !std::isalnum(c); })) [[unlikely]] {
|
||
return false;
|
||
} else if (std::search(std::cbegin(str), std::cend(str), std::cbegin(kResult), std::cend(kResult)) ==
|
||
std::cend(str)) [[likely]] {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool validate2(const std::string& str) {
|
||
constexpr char kResult[] = "result:";
|
||
|
||
if (str.empty()) {
|
||
return false;
|
||
} else if (str.size() > 30) {
|
||
return false;
|
||
} else if (str.front() != '{') {
|
||
return false;
|
||
} else if (str.back() != '}') {
|
||
return false;
|
||
} else if (std::all_of(std::cbegin(str), std::cend(str), [](const char c) { return !std::isalnum(c); })) {
|
||
return false;
|
||
} else if (std::search(std::cbegin(str), std::cend(str), std::cbegin(kResult), std::cend(kResult)) ==
|
||
std::cend(str)) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
int main() {
|
||
auto benchmark = [](auto fun) {
|
||
const auto start = std::chrono::high_resolution_clock::now();
|
||
fun();
|
||
const auto diff = std::chrono::high_resolution_clock::now() - start;
|
||
std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() << " ns\n";
|
||
};
|
||
|
||
int wrong = 0;
|
||
std::vector<std::string> vec(1'000'000, "{result:12345}");
|
||
benchmark([&]() {
|
||
wrong += std::count_if(std::cbegin(vec), std::cend(vec), [](const auto& str) { return validate(str); });
|
||
});
|
||
benchmark([&]() {
|
||
wrong += std::count_if(std::cbegin(vec), std::cend(vec), [](const auto& str) { return validate2(str); });
|
||
});
|
||
// Time: 293222100 ns
|
||
// Time: 376011000 ns
|
||
//
|
||
// With 03
|
||
// Time: 14437300 ns
|
||
// Time: 21943400 ns
|
||
|
||
return wrong;
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.67em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## no_unique_address
|
||
|
||
Allows this data member to be overlapped with other non-static data members or base class subobjects of its class. Generally, we can use it when we have an empty filed in the structure, and we don't want to lost 1 byte to store it.
|
||
|
||
```C++
|
||
struct Empty {};
|
||
|
||
struct Filed {
|
||
int val_;
|
||
double val2_;
|
||
Empty empty_;
|
||
};
|
||
|
||
struct Filed2 {
|
||
int val_;
|
||
double val2_;
|
||
[[no_unique_address]] Empty empty_;
|
||
};
|
||
|
||
int main() {
|
||
static_assert(sizeof(Empty) >= 1);
|
||
std::cout << "sizeof(Filed): " << sizeof(Filed) << '\n'; // 24
|
||
std::cout << "sizeof(Filed): " << sizeof(Filed2) << '\n'; // 16
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.80em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## pack-expansion in lmabdas
|
||
|
||
Since C++20 we can easily move all arguments to lambda. Before C++20 we can do this only for one object, but now it works for packages also.
|
||
|
||
```C++
|
||
auto postponeTask(auto&& fun, auto&&... args) {
|
||
return [f = std::move(fun), ... pack = std::move(args)]() {
|
||
return f(pack...);
|
||
};
|
||
}
|
||
|
||
int main() {
|
||
auto task = postponeTask([](const std::string& str, int num) {
|
||
std::cout << str << " | " << num << '\n';
|
||
}, std::string("Ala ma kota"), 42);
|
||
|
||
task();
|
||
}
|
||
```
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
If you don't have C++20 (but you have C++17) you can still use the trick with the `std::tuple` with `std::apply` method.
|
||
|
||
```C++
|
||
template <typename Fun, typename... Args>
|
||
auto postponeTask(Fun fun, Args... args) {
|
||
return [fun = std::move(fun), tup = std::make_tuple(std::move(args)...)]() -> decltype(auto) {
|
||
return std::apply([fun = std::move(fun)](auto const&... args) -> decltype(auto) {
|
||
return fun(args...);
|
||
}, tup);
|
||
};
|
||
}
|
||
int main() {
|
||
auto task = postponeTask([](const std::string& str, int num) {
|
||
std::cout << str << " | " << num << '\n';
|
||
}, std::string("Ala ma kota"), 42);
|
||
|
||
task();
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.85em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## template syntax for lambdas
|
||
|
||
Ok, but what if I want to perfect forward arguments, instead of moving them? Since C++20 you can use template syntax!
|
||
|
||
```C++
|
||
int main() {
|
||
auto benchmark = []<typename... Args>(auto&& fun, Args... args) {
|
||
const auto now = std::chrono::system_clock::now();
|
||
const auto res = std::move(fun)(std::forward<Args>(args)...);
|
||
const auto then = std::chrono::system_clock::now();
|
||
std::cout << "res: " << res << " | time: " << (then - now).count() << " ns\n";
|
||
};
|
||
|
||
benchmark([](int count, int init) -> long long {
|
||
std::vector<int> vec(count);
|
||
std::iota(begin(vec), end(vec), init);
|
||
return std::accumulate(begin(vec), end(vec), 0);
|
||
}, 1'000'000, 50);
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.90em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## uniform erasure
|
||
|
||
Ok let's do sth easier! How many times have you written sth like this :)?
|
||
|
||
```C++
|
||
std::vector<int> vec {1,2,3,4,5,6};
|
||
vec.erase(std::remove_if(begin(vec), end(vec), [](auto num){ return num & 1; }), vec.end());
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
`std::remove` only prepares a vector to actually remove, but in the end, we need to erasure these values. Yes, this is why a lot of ppl hate C++, always complications. For instance we can write sth like this:
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
std::map<std::string, int> map {{"One", 1}, {"Two", 2}, {"Three", 3}};
|
||
map.erase("One");
|
||
|
||
std::list<std::string> list {"Ala", "ma", "kota"};
|
||
list.remove("Ala");
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
In the case of a list, there is a member function `remove`, which actually removes an element! But for the map, we have only `erase`, which also takes a key as a value and removes an element. 3 containers and 3 different behavior. Definitely, newcomers won't like this. The funniest part is that list has `remove_if` method, but the map doesn't have `erase_if` ;)
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.82em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
Since C++20 erasing elements is finally unified! We have two options, `erase` or `erase_if`. Unfortunately, in C++ there is always an exception to the rule. `std::map` and `std::set` (and their hash versions) don't have `std::erase` overload, because they already have specialized members functions.
|
||
|
||
```C++
|
||
std::vector<int> vec {1,2,3,4,5,6};
|
||
std::erase(vec, 4);
|
||
std::erase_if(vec, [](auto num){ return num & 1; });
|
||
|
||
std::map<std::string, int> map {{"One", 1}, {"Two", 2}, {"Three", 3}};
|
||
map.erase("One"); // There is no std::erase() for map :/
|
||
std::erase_if(map, [](const auto& pair){ return pair.second == 2; }); // But there is std::erase_if
|
||
|
||
std::list<std::string> list {"Ala", "ma", "kota"};
|
||
std::erase(list, "ma");
|
||
std::erase_if(list, [](const auto& str){ return str.length() == 3; });
|
||
|
||
std::unordered_set<int> set{1,2,3,4,5,6};
|
||
set.erase(4);
|
||
std::erase_if(set, [](auto num){ return num & 1; });
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.81em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## source_loaction
|
||
|
||
Since C++20 we got a nice functionality to perform easy and efficient logging.
|
||
|
||
```C++
|
||
void log(const std::string_view message,
|
||
const std::source_location location =
|
||
std::source_location::current())
|
||
{
|
||
std::cout << "file: "
|
||
<< location.file_name() << "("
|
||
<< location.line() << ":"
|
||
<< location.column() << ") `"
|
||
<< location.function_name() << "`: "
|
||
<< message << '\n';
|
||
}
|
||
|
||
template <typename T> void fun(T x)
|
||
{
|
||
log(x);
|
||
}
|
||
|
||
int main(int, char*[])
|
||
{
|
||
log("Hello world!");
|
||
fun("Hello C++20!");
|
||
}
|
||
```
|
||
|
||
```bash
|
||
file: prog.cc(24:8) `int main(int, char**)`: Hello world!
|
||
file: prog.cc(19:8) `void fun(T) [with T = const char*]`: Hello C++20!
|
||
```
|
||
<!-- .slide: style="font-size: 0.75em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
It's easy to rebuild a little this solution and create own logger:
|
||
|
||
```C++
|
||
enum LogType {INF, WRN, ERR};
|
||
|
||
struct Logger {
|
||
Logger(LogType logType, std::source_location location = std::source_location::current()):
|
||
logType_{logType},
|
||
location_{location} {}
|
||
|
||
Logger& operator<<(std::string_view message) {
|
||
const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||
std::ostringstream os;
|
||
os << "[" << toString(logType_) << "]"
|
||
<< "(" << std::put_time(std::localtime(&time), "%Y-%m-%d %X") << "): "
|
||
<< location_.file_name() << "("
|
||
<< location_.line() << ":"
|
||
<< location_.column() << ") `"
|
||
<< location_.function_name() << "`: "
|
||
<< message << '\n';
|
||
file_ << os.str() << std::flush;
|
||
|
||
return *this;
|
||
}
|
||
|
||
private:
|
||
std::string_view toString(LogType logType) {
|
||
switch (logType) {
|
||
case INF: return "INFO";
|
||
case WRN: return "WARNING";
|
||
case ERR: return "ERROR";
|
||
default: return "UNKNOWN!";
|
||
}
|
||
}
|
||
|
||
LogType logType_;
|
||
std::source_location location_;
|
||
static std::ofstream file_;
|
||
};
|
||
|
||
std::ofstream Logger::file_("log.txt");
|
||
```
|
||
<!-- .slide: style="font-size: 0.75em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
Ok now is time to log sth
|
||
|
||
```C++
|
||
int main() {
|
||
Logger(INF) << "Hello!" << "And Hi" << "And Dzien dobry!";
|
||
Logger(WRN) << "Ojej!";
|
||
Logger(ERR) << "Critical!!!" << "UPS!" << "Bad bad!";
|
||
}
|
||
```
|
||
|
||
And in file `log.txt` we got:
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
[INFO](2022-05-22 14:24:47): prog.cc(55:15) `int main()`: Hello!
|
||
[INFO](2022-05-22 14:24:47): prog.cc(55:15) `int main()`: And Hi
|
||
[INFO](2022-05-22 14:24:47): prog.cc(55:15) `int main()`: And Dzien dobry!
|
||
[WARNING](2022-05-22 14:24:47): prog.cc(56:15) `int main()`: Ojej!
|
||
[ERROR](2022-05-22 14:24:47): prog.cc(57:15) `int main()`: Critical!!!
|
||
[ERROR](2022-05-22 14:24:47): prog.cc(57:15) `int main()`: UPS!
|
||
[ERROR](2022-05-22 14:24:47): prog.cc(57:15) `int main()`: Bad bad!
|
||
```
|
||
<!-- .slide: style="font-size: 0.86em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
# bit operations
|
||
|
||
There is a new header `<bit>` which allows performing many valuable operations on bits. All functions are declared as constexpr, so there is a big chance that everything will be evaluated at compile time! Finally, you don't need to write an ugly macro. Let's start with the most important one.
|
||
|
||
`std::endian` indicates the endianness of all scalar types:
|
||
|
||
- <!-- .element: class="fragment fade-in" --> If all scalar types are little-endian, std::endian::native equals std::endian::little
|
||
- <!-- .element: class="fragment fade-in" --> If all scalar types are big-endian, std::endian::native equals std::endian::big
|
||
|
||
Corner case platforms are also supported:
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
- <!-- .element: class="fragment fade-in" --> If all scalar types have sizeof equal to 1, endianness does not matter and all three values, std::endian::little, std::endian::big, and std::endian::native are the same.
|
||
- <!-- .element: class="fragment fade-in" --> If the platform uses mixed endian, std::endian::native equals neither std::endian::big nor std::endian::little.
|
||
|
||
```C++
|
||
int main() {
|
||
|
||
if constexpr (std::endian::native == std::endian::big)
|
||
std::cout << "big-endian\n";
|
||
else if constexpr (std::endian::native == std::endian::little)
|
||
std::cout << "little-endian\n";
|
||
else std::cout << "mixed-endian\n";
|
||
// Output: little-endian
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.70em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
For me very usefull is also `popcount`, and all `countX_Y` function, where`X` could be `l` - left, `r` - right, and `Y` could be `zero` or `one`:
|
||
|
||
```C++
|
||
int main() {
|
||
// counts the number of 1 bits in an unsigned integer
|
||
std::cout << std::popcount(0b1010101010101u) << '\n'; // 7
|
||
|
||
// counts the number of consecutive 1 bits, starting from the most significant bit
|
||
std::cout << std::countl_one(std::numeric_limits<uint8_t>::max()) << '\n'; // 8
|
||
|
||
// counts the number of consecutive 0 bits, starting from the most significant bit
|
||
std::cout << std::countl_zero(0b111000011111u) << '\n'; // 20 because this is uint32_t
|
||
|
||
// counts the number of consecutive 1 bits, starting from the least significant bit
|
||
std::cout << std::countr_one(0b111000011111u) << '\n'; // 5
|
||
|
||
// counts the number of consecutive 0 bits, starting from the least significant bit
|
||
std::cout << std::countr_zero(0b111000011100u) << '\n'; // 2
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.90em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
Sometimes in the algorithm, we need to know the closest power of 2 for a given number.
|
||
|
||
```C++
|
||
int main() {
|
||
for (uint8_t i = 0 ; i < 16 ; ++i) {
|
||
std::cout << "num: " << std::bitset<sizeof(i) << 3>(i)
|
||
<< " | bit_width: " << +std::bit_width(i)
|
||
<< " | power of 2: " << std::has_single_bit(i)
|
||
<< " | floor: " << +std::bit_floor(i)
|
||
<< " | ceil: " << +std::bit_ceil(i) << '\n';
|
||
}
|
||
}
|
||
```
|
||
```bash
|
||
num: 00000000 | bit_width: 0 | power of 2: 0 | floor: 0 | ceil: 1
|
||
num: 00000001 | bit_width: 1 | power of 2: 1 | floor: 1 | ceil: 1
|
||
num: 00000010 | bit_width: 2 | power of 2: 1 | floor: 2 | ceil: 2
|
||
num: 00000011 | bit_width: 2 | power of 2: 0 | floor: 2 | ceil: 4
|
||
num: 00000100 | bit_width: 3 | power of 2: 1 | floor: 4 | ceil: 4
|
||
num: 00000101 | bit_width: 3 | power of 2: 0 | floor: 4 | ceil: 8
|
||
num: 00000110 | bit_width: 3 | power of 2: 0 | floor: 4 | ceil: 8
|
||
num: 00000111 | bit_width: 3 | power of 2: 0 | floor: 4 | ceil: 8
|
||
num: 00001000 | bit_width: 4 | power of 2: 1 | floor: 8 | ceil: 8
|
||
num: 00001001 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
num: 00001010 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
num: 00001011 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
num: 00001100 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
num: 00001101 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
num: 00001110 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
num: 00001111 | bit_width: 4 | power of 2: 0 | floor: 8 | ceil: 16
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.80em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
There is also a rotation `left` /` right`. Standard `c ++ 23` introduced also` byteswap` to convert variables from little endian to big endian and vice versa.
|
||
|
||
```C++
|
||
uint16_t num = 0b1111001100111001;
|
||
std::cout << "num: " << std::bitset<16>(num) << "\n\n";
|
||
for (uint8_t i = 0 ; i < 16 ; ++i) {
|
||
std::cout << "rotr: " << std::bitset<16>(std::rotr(num, i))
|
||
<< " | rotl: " << std::bitset<16>(std::rotl(num, i))
|
||
<< '\n';
|
||
}
|
||
```
|
||
|
||
```bash
|
||
num: 1111001100111001
|
||
|
||
rotr: 1111001100111001 | rotl: 1111001100111001
|
||
rotr: 1111100110011100 | rotl: 1110011001110011
|
||
rotr: 0111110011001110 | rotl: 1100110011100111
|
||
rotr: 0011111001100111 | rotl: 1001100111001111
|
||
rotr: 1001111100110011 | rotl: 0011001110011111
|
||
rotr: 1100111110011001 | rotl: 0110011100111110
|
||
rotr: 1110011111001100 | rotl: 1100111001111100
|
||
rotr: 0111001111100110 | rotl: 1001110011111001
|
||
rotr: 0011100111110011 | rotl: 0011100111110011
|
||
rotr: 1001110011111001 | rotl: 0111001111100110
|
||
rotr: 1100111001111100 | rotl: 1110011111001100
|
||
rotr: 0110011100111110 | rotl: 1100111110011001
|
||
rotr: 0011001110011111 | rotl: 1001111100110011
|
||
rotr: 1001100111001111 | rotl: 0011111001100111
|
||
rotr: 1100110011100111 | rotl: 0111110011001110
|
||
rotr: 1110011001110011 | rotl: 1111100110011100
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.75em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## format your string like in <code>printf</code>
|
||
|
||
The main problem with `std::cout` is a problem with easy formatting (I know streams are also inefficient, but this is not a point right now). For instance:
|
||
|
||
```C++
|
||
int main() {
|
||
for (uint8_t i = 0 ; i < 16 ; ++i) {
|
||
std::cout << "num: " << std::bitset<sizeof(i) << 3>(i)
|
||
<< " | bit_width: " << +std::bit_width(i)
|
||
<< " | power of 2: " << std::has_single_bit(i)
|
||
<< " | floor: " << +std::bit_floor(i)
|
||
<< " | ceil: " << +std::bit_ceil(i) << '\n';
|
||
}
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
With `<format>` library now we can write:
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
for (uint8_t i = 0; i < 16; ++i) {
|
||
std::cout << std::format("num: {} | bit_width: {} | power of 2: {} | floor: {} | ceil {}\n",
|
||
std::bitset<sizeof(i) << 3>(i).to_string(),
|
||
std::bit_width(i),
|
||
std::has_single_bit(i),
|
||
std::bit_floor(i),
|
||
std::bit_ceil(i));
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.75em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
On day `25.05.2022` the newest `gcc 13` didn't support the library `<format>`. Fortunately `clang 15` already supports it! `<Format>` library is very powerful, we can easily create any format we want and pass any values we need:
|
||
|
||
```C++
|
||
void raw_write_to_log(std::string_view users_fmt, std::format_args&& args) {
|
||
constinit static int line{};
|
||
std::clog << std::format("{:04} : ", line++) << std::vformat(users_fmt, args) << '\n';
|
||
}
|
||
|
||
template <typename... Args>
|
||
constexpr void log(Args&&... args) {
|
||
// Generate formatting string "{} "...
|
||
std::array<char, sizeof...(Args) * 3 + 1> braces{};
|
||
constexpr const char c[] = "{} ";
|
||
for (auto i{0u}; i != braces.size() - 1; ++i) {
|
||
braces[i] = c[i % 3];
|
||
}
|
||
braces.back() = '\0';
|
||
|
||
raw_write_to_log(braces.data(), std::make_format_args(std::forward<Args>(args)...));
|
||
}
|
||
|
||
int main()
|
||
{
|
||
std::string str{"Printable"};
|
||
log("You", "Can", "Pass", "Any", "Number", "Of", "Arguments");
|
||
log("Everything", "Which", "Can", "Be", str);
|
||
log(1, 4.5, 1234);
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
```bash
|
||
0000 : You Can Pass Any Number Of Arguments
|
||
0001 : Everything Which Can Be Printable
|
||
0002 : 1 4.5 1234
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.68em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
## Limitations
|
||
|
||
We need to wait to specify how we should specify custom object formatting. Currently on cppreference we can see an example, but looking very ugly and don't compile on `clang 15`.
|
||
|
||
```C++
|
||
#include <format>
|
||
#include <iostream>
|
||
|
||
// A wrapper for type T
|
||
template<class T>
|
||
struct Box {
|
||
T value;
|
||
};
|
||
|
||
// The wrapper Box<T> can be formatted using the format specification of the wrapped value
|
||
template<class T, class CharT>
|
||
struct std::formatter<Box<T>, CharT> : std::formatter<T, CharT> {
|
||
// parse() is inherited from the base class
|
||
|
||
// Define format() by calling the base class implementation with the wrapped value
|
||
template<class FormatContext>
|
||
auto format(Box<T> t, FormatContext& fc) const {
|
||
return std::formatter<T, CharT>::format(t.value, fc);
|
||
}
|
||
};
|
||
|
||
int main() {
|
||
Box<int> v = { 42 };
|
||
std::cout << std::format("{:#x}", v);
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.72em" -->
|
||
___
|
||
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
|
||
|
||
But to make you more interested in this library check out more possibilities:
|
||
|
||
```C++
|
||
int main()
|
||
{
|
||
std::string buffer;
|
||
|
||
std::format_to(
|
||
std::back_inserter(buffer),
|
||
"Hello, C++{}!\n", // formater
|
||
"20", // args
|
||
"More args, make no error :)");
|
||
|
||
std::cout << buffer << '\n';
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```bash
|
||
Hello, C++20!
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.82em" -->
|