373 lines
No EOL
10 KiB
Markdown
373 lines
No EOL
10 KiB
Markdown
# 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 <typename T>
|
||
void function(T& arg) {}
|
||
|
||
template <typename T>
|
||
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 <typename T>
|
||
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 <typename T>
|
||
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" --> |