trainings/AdvancedCppV2/Presentation/template_deduction_guides.md

373 lines
No EOL
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Deduction guides
___
## Template deduction
There are three different scenarios during template type deduction:
* <!-- .element: class="fragment fade-in" --> Handle by reference or pointer: <code>T&</code> or <code>T*</code> with or without <code>const</code> or <code>volatile</code>
* <!-- .element: class="fragment fade-in" --> Handle by value: <code>T</code> with or without <code>const</code> or <code>volatile</code>
* <!-- .element: class="fragment fade-in" --> Handle by universal reference: <code>T&&</code> can't use <code>const</code> or <code>volatile</code> here.
___
## Pass by reference
<pre><code class="cpp" data-trim data-noescape>
template &lt;typename T&gt;
void function(T& arg) {}
template &lt;typename T&gt;
void constFunction(const T& arg) {}
void foo(int) {}
int main() {
int a = 4;
const int b = 5;
const int& c = a;
int arr[] = {1,2};
function(a); <span class="fragment">// T -> int | arg -> int&</span>
function(5); <span class="fragment">// Not compile!</span>
function(b); <span class="fragment">// T -> const int | arg -> const int&</span>
function(c); <span class="fragment">// T -> const int | arg -> const int&</span>
function(foo); <span class="fragment">// T -> void(int) | arg -> void(&)(int)</span>
function(arr); <span class="fragment">// T -> int[2] | arg -> int(&)[2]</span>
constFunction(a); <span class="fragment">// T -> int | arg -> const int&</span>
constFunction(5); <span class="fragment">// T -> int | arg -> const int&</span>
constFunction(b); <span class="fragment">// T -> int | arg -> const int&</span>
constFunction(c); <span class="fragment">// T -> int | arg -> const int&</span>
constFunction(foo); <span class="fragment">// T -> void(int) | arg -> const void(&)(int)</span>
constFunction(arr); <span class="fragment">// T -> int[2] | arg -> const int(&)[2]</span>
}
</code></pre>
<!-- .slide: style="font-size: 0.70em" -->
___
## Pass by value
<pre><code class="cpp" data-trim data-noescape>
template &lt;typename T&gt;
void function(T arg) {}
void foo(int) {}
int main() {
int a = 4;
const int b = 5;
const int& c = a;
int arr[] = {1,2};
char name[] = "Mateusz";
const char* str = name;
const char* const ptr = name;
function(a); <span class="fragment">// T -> int | arg -> int</span>
function(5); <span class="fragment">// T -> int | arg -> int</span>
function(b); <span class="fragment">// T -> int | arg -> int</span>
function(c); <span class="fragment">// T -> int | arg -> int</span>
function(foo); <span class="fragment">// T -> void(*)(int) | arg -> void(*)(int))</span>
function(arr); <span class="fragment">// T -> int* | arg -> int*</span>
function(str); <span class="fragment">// T -> const char* | arg -> const char*</span>
function(ptr); <span class="fragment">// T -> const char* | arg -> const char*</span>
}
</code></pre>
<!-- .slide: style="font-size: 0.80em" -->
___
## Pass by universal reference
<pre><code class="cpp" data-trim data-noescape>
template &lt;typename T&gt;
void function(T&& arg) {}
void foo(int) {}
int main() {
int a = 4;
const int b = 5;
const int& c = a;
int arr[] = {1,2};
char name[] = "Mateusz";
const char cstr[] = "Mateusz";
const char* str = name;
const char* const ptr = name;
function(a); <span class="fragment">// T -> int& | arg -> int&</span>
function(5); <span class="fragment">// T -> int | arg -> int&&</span>
function(b); <span class="fragment">// T -> const int& | arg -> const int&</span>
function(c); <span class="fragment">// T -> const int& | arg -> const int&</span>
function(foo); <span class="fragment">// T -> void(&)(int) | arg -> void(&)(int))</span>
function(arr); <span class="fragment">// T -> int(&)[2] | arg -> int(&)[2]</span>
function(cstr); <span class="fragment">// T -> const char(&)[8] | arg -> const char(&)[8]</span>
function(str); <span class="fragment">// T -> const char(*&) | arg -> const char(*&)</span>
function(std::move(str)); <span class="fragment">// T -> const char(*) | arg -> const char(*&&)</span>
function(ptr); <span class="fragment">// T -> const char(*const &) | arg -> const char(*const &)</span>
}
</code></pre>
<!-- .slide: style="font-size: 0.74em" -->
___
## Pass by universal reference - special treatment
When template parameter gets argument by universal reference, deducted type `T` doesn't remove the reference for `l-values`.
In other words: `r-values` are treated as they are passed by value, but `l-values` are treated as a reference.
This is partially true. Scott Meyers said this is an abstraction layer. The real truth is reference collapsing:
<!-- .element: class="fragment fade-in" -->
* <!-- .element: class="fragment fade-in" --> <code>T& &</code> -> <code>T&</code>
* <!-- .element: class="fragment fade-in" --> <code>T& &&</code> -> <code>T&</code>
* <!-- .element: class="fragment fade-in" --> <code>T&& &</code> -> <code>T&</code>
* <!-- .element: class="fragment fade-in" --> <code>T&& &&</code> -> <code>T&&</code>
___
## auto deduction
`auto` deduction works similar to templates, but there is one exception, which you should remember from previous slajds.
<div class="multicolumn">
<div class="col">
```C++
auto val = 5;
```
is equal to
```C++
template <typename T>
void foo(T val);
```
</div>
<!-- .element: class="fragment fade-in" -->
<div class="col">
```C++
const auto& val = 5;
```
is equal to
```C++
template <typename T>
void foo(const T& val);
```
</div>
<!-- .element: class="fragment fade-in" -->
<div class="col">
```C++
auto&& val = 5;
```
is equal to
```C++
template <typename T>
void foo(T&& val);
```
</div>
<!-- .element: class="fragment fade-in" -->
</div>
___
## auto deduction - one exception
```C++
template <typename T>
void foo(T t) {}
auto val = {1, 2, 3, 4}; // std::initializer_list<int>
foo({1, 2, 3, 4}); // deduction failed!
```
<!-- .element: class="fragment fade-in" -->
Need to explicity use `initializer_list`
<!-- .element: class="fragment fade-in" -->
```C++
template <typename T>
void foo(std::initializer_list<T> t) {}
auto val = {1, 2, 3, 4}; // std::initializer_list<int>
foo({1, 2, 3, 4}); // std::initializer_list<int>
```
<!-- .element: class="fragment fade-in" -->
___
## auto in generic lambda
In generic lambda `auto` uses the same deduction rules like for templates not for `auto`! This happens because, lambda is struct, so generic lambda is a template structure.
<div class="col">
```C++
auto lambda = [](auto&& first, const auto& second, auto third) {}
```
is equal to
```C++
struct Lmabda {
template <typename X, typename Y, typename Z>
auto operator()(X&& x, const Y& y, Z z) const {
}
};
```
</div>
<!-- .element: class="fragment fade-in" -->
___
## std::forward once more
If we want to perfect forward some value in template you will write:
<!-- .element: class="fragment fade-in" -->
```C++
template <typename T>
void fun(T&& t) {
other(std::forward<T>(t));
}
```
<!-- .element: class="fragment fade-in" -->
But how to do this in lambda? We know that generic lambda is a teplate, but we don't have an access to `T`!
<!-- .element: class="fragment fade-in" -->
```C++
auto lambda = [](auto&& t) {
other(std::forward<decltype(t)>(t));
};
```
<!-- .element: class="fragment fade-in" -->
___
## decltype
Decltype return a type of variable, without removing `references` or `const`/ `volatile` qualifiers
```C++
int x = 5;
decltype(x) y; // int
const int num = 20;
decltype(num) num2 = 30; // const int
const int& ref = num;
decltype(ref) ref2 = x; // const int&
const char name[] = "Mateusz";
decltype(name) name2 = "Scott"; // const char[]
decltype(foo) fun; // void fun(int, const string&
auto pred = [](int num){ return num % 1 == 0; };
decltype(pred(20)) val; // bool
std::vector<int> vec{1};
decltype(vec.begin()) it; // std::vector<int>::iterator
decltype(vec[0]) // int&
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.8em" -->
___
## decltype - one problem
What is wrong with this snippet of code?
```C++
void authorize() {}
template <typename C>
auto authorizeAndAccess(C& container, size_t index) {
authorize();
return container[index];
}
int main() {
std::vector<int> vec{1,2,3};
authorizeAndAccess(vec, 2) = 10;
std::cout << vec[2] << '\n';
}
```
<!-- .element: class="fragment fade-in" -->
```C++
error: lvalue required as left operand of assignment authorizeAndAccess(vec, 2) = 10;
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.8em" -->
___
## decltype - partial solution
The same result we can achieve by using `decltype(auto)`.
<!-- .element: class="fragment fade-in" -->
```C++
void authorize() {}
template <typename C>
auto authorizeAndAccess(C& container, size_t index) -> decltype(container[index]) {
authorize();
return container[index];
}
int main() {
std::vector<int> vec{1,2,3};
authorizeAndAccess(vec, 2) = 10;
std::cout << vec[2] << '\n';
}
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.8em" -->
___
## decltype - when solution make another trouble
What is wrong now?
<!-- .element: class="fragment fade-in" -->
```C++
void authorize() {}
template <typename C>
decltype(auto) authorizeAndAccess(C& container, size_t index) {
authorize();
return container[index];
}
int main() {
const auto res = authorizeAndAccess(std::vector<int>{5, 8, 12, 16}, 2);
std::cout << std::boolalpha << "res: " << res << '\n';
}
```
<!-- .element: class="fragment fade-in" -->
```C++
cannot bind non-const lvalue reference of type std::vector<vec>& to an rvalue of type std::vector<int>
const auto res = authorizeAndAccess(std::vector<int>{5, 8, 12, 16}, 2);
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.8em" -->
___
## decltype - final fix
```C++
void authorize() {}
template <typename C>
decltype(auto) authorizeAndAccess(C&& container, size_t index) {
authorize();
return std::forward<C>(container)[index];
}
int main() {
const auto res = authorizeAndAccess(std::vector<int>{5, 8, 12, 16}, 2);
std::cout << std::boolalpha << "res: " << res << '\n'; // will print 12
}
```
<!-- .slide: style="font-size: 0.95em" -->