trainings/AdvancedCppV2/Presentation/moder_cpp_cpp14.md

256 lines
7.4 KiB
Markdown

## Cpp14
<!-- .slide: data-background="#ccc" -->
* <!-- .element: class="fragment fade-in" --> A quick reminder of lesser known features
* <!-- .element: class="fragment fade-in" --> decltype(auto)
* <!-- .element: class="fragment fade-in" --> Variable templates
* <!-- .element: class="fragment fade-in" --> Binary literals (Finaly!)
* <!-- .element: class="fragment fade-in" --> Digit separators
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
## `decltype`
**Rationale**: Deduction provided in contexts where auto is not allowed.
<!-- .element: class="fragment fade-in" -->
`decltype` allows a compiler to deduce the type of the variable or expression, eg. the returned type can be deduced from function parameters.
<!-- .element: class="fragment fade-in" -->
```cpp
std::map<std::string, float> collection;
decltype(collection) other; // other has type of collection
decltype(collection)::mapped_type value; // value is float
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) // from C++14 decltype not necessary
{
return a + b;
}
```
<!-- .element: class="fragment fade-in" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
It is useful in various scenarios:
```C++
auto compare = [](const auto &first, const auto &second) {
if (first.size() == second.size()) {
return first < second;
}
return first.size() < second.size();
};
std::map<std::string, int, decltype(compare)> map(compare);
map.emplace("C++20", 20);
map.emplace("C++1234", 1234);
map.emplace("Bababab", 12);
map.emplace("Abababa", 13);
for (const auto &[standard, number] : map) {
std::cout << "Standard: " << standard << " | number: " << number << '\n';
/* Output:
Standard: C++20 | number: 20
Standard: Abababa | number: 13
Standard: Bababab | number: 12
Standard: C++1234 | number: 1234 */
}
```
<!-- .slide: style="font-size: 0.90em" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
Since C++20 we can use lambda expressions in unevaluated operands:
```C++
using SquareRoot = decltype([](const int val) {
return std::sqrt(val);
});
using Compare = decltype([](const auto &first, const auto &second) {
if (first.size() == second.size()) {
return first < second;
}
return first.size() < second.size();
});
int main() {
std::vector<int> vec(30);
std::iota(begin(vec), end(vec), 0);
std::transform(begin(vec), end(vec), begin(vec), SquareRoot{});
for (const auto& el : vec) {
std::cout << el << ' ';
}
// Output:
// 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5
// Compare lambda will be constructed by default
std::map<std::string, int, Compare> map;
map.emplace("C++20", 20);
map.emplace("C++1234", 1234);
map.emplace("Bababab", 12);
map.emplace("Abababa", 13);
}
```
Closure types are not default constructible before C++20. In C++20 a closure type that has no capture is default constructible. That's why we can do a litle magic here :)
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.70em" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
## `decltype(auto)`
`decltype(auto)` deduction mechanism preserves type modifiers (references, const, volatile).
<!-- .element: class="fragment fade-in" -->
`auto` deduction mechanism does not preserve type modifiers.
<!-- .element: class="fragment fade-in" -->
When you write generic code you want to be able to perfectly forward a return type without knowing whether you are dealing with a reference or a value.
<!-- .element: class="fragment fade-in" -->
```cpp
template<typename Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}
```
<!-- .element: class="fragment fade-in" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
## Let's test it
We have the following class:
```C++
class Server {
public:
bool addRequest(const std::string& serviceId, const std::string& request) {
return requests_.emplace(serviceId, request).second;
}
std::string& getRequest(const std::string& serviceId) {
if (const auto it = requests_.find(serviceId) ; it != std::cend(requests_)) {
return it->second;
}
throw std::runtime_error("Invalid serviceId");
}
private:
std::map<std::string, std::string> requests_;
};
int main() {
Server server;
server.addRequest("SuperService", "Eat meat first!");
server.getRequest("SuperService") += " Leave the potatoes";
std::cout << server.getRequest("SuperService") << '\n';
// Eat meat first! Leave the potatoes
}
```
<!-- .slide: style="font-size: 0.74em" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
Now let's use it with generic function, but without `decltype(auto)`
```C++
template<typename Fun, class... Args>
auto RunFun(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}
int main() {
Server server;
server.addRequest("SuperService", "Eat meat first!");
// Write that we want to return std::string&
RunFun([&server](const auto& id) -> std::string& { return server.getRequest(id); },
"SuperService") += " Leave the potatoes";
std::cout << server.getRequest("SuperService") << '\n';
// Output: Eat meat first!
// Compiler didn't emit any warning!
}
```
<!-- .slide: style="font-size: 0.80em" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
Now fix this with `decltype(auto)`
```C++
template<typename Fun, class... Args>
decltype(auto) RunFun(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}
int main() {
Server server;
server.addRequest("SuperService", "Eat meat first!");
// Write that we want to return std::string&
RunFun([&server](const auto& id) -> std::string& { return server.getRequest(id); },
"SuperService") += " Leave the potatoes";
std::cout << server.getRequest("SuperService") << '\n';
// Output: Eat meat first! Leave the potatoes
}
```
Now we avoid misleading and don't waste time on debugging sessions!
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.80em" -->
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
## Variable templates
```C++
template <typename T>
constexpr T pi = T(3.141592653589793238462643383);
// Usual specialization rules apply:
template <>
constexpr const char* pi<const char*> = "pi";
template <>
constexpr const int pi<const int> = 4;
int main() {
std::cout << pi<double> << '\n'; // 3.14159
std::cout << pi<const char*> << '\n'; // pi
std::cout << pi<int> << '\n'; // 3
std::cout << pi<const int> << '\n'; // 4
return 0;
}
```
___
<!-- .slide: data-background="#ccc" --><!-- .slide: data-background="#ccc" -->
## Binary literals
```C++
int main() {
std::cout << 0b10101010 << '\n'; // 170
const int val = 0b1111;
std::cout << (val ^ 0b1010) << '\n'; // 5
return 0;
}
```
<!-- .element: class="fragment fade-in" -->
## Digit separators
<!-- .element: class="fragment fade-in" -->
```C++
const int milion = 1'000'000;
const double val = 123'456'789'101.000;
```
<!-- .element: class="fragment fade-in" -->