# Deduction guides ___ ## Template deduction There are three different scenarios during template type deduction: * Handle by reference or pointer: T& or T* with or without const or volatile * Handle by value: T with or without const or volatile * Handle by universal reference: T&& can't use const or volatile here. ___ ## Pass by reference

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);        // T -> int | arg -> int&
    function(5);        // Not compile!
    function(b);        // T -> const int | arg -> const int&
    function(c);        // T -> const int | arg -> const int&
    function(foo);      // T -> void(int) | arg -> void(&)(int)
    function(arr);      // T -> int[2] | arg -> int(&)[2]
    constFunction(a);   // T -> int | arg -> const int&
    constFunction(5);   // T -> int | arg -> const int&
    constFunction(b);   // T -> int | arg -> const int&
    constFunction(c);   // T -> int | arg -> const int&
    constFunction(foo); // T -> void(int) | arg -> const void(&)(int)
    constFunction(arr); // T -> int[2] | arg -> const int(&)[2]
}
___ ## Pass by value

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);        // T -> int | arg -> int
    function(5);        // T -> int | arg -> int
    function(b);        // T -> int | arg -> int
    function(c);        // T -> int | arg -> int
    function(foo);      // T -> void(*)(int) | arg -> void(*)(int))
    function(arr);      // T -> int* | arg -> int*
    function(str);      // T -> const char* | arg -> const char*
    function(ptr);      // T -> const char* | arg -> const char*
}
___ ## Pass by universal reference

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);              // T -> int& | arg -> int&
    function(5);              // T -> int | arg -> int&&
    function(b);              // T -> const int& | arg -> const int&
    function(c);              // T -> const int& | arg -> const int&
    function(foo);            // T -> void(&)(int) | arg -> void(&)(int))
    function(arr);            // T -> int(&)[2] | arg -> int(&)[2]
    function(cstr);           // T -> const char(&)[8] | arg -> const char(&)[8]
    function(str);            // T -> const char(*&) | arg -> const char(*&)
    function(std::move(str)); // T -> const char(*) | arg -> const char(*&&)
    function(ptr);            // T -> const char(*const &) | arg -> const char(*const &)
}
___ ## 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: * T& & -> T& * T& && -> T& * T&& & -> T& * T&& && -> T&& ___ ## auto deduction `auto` deduction works similar to templates, but there is one exception, which you should remember from previous slajds.
```C++ auto val = 5; ``` is equal to ```C++ template void foo(T val); ```
```C++ const auto& val = 5; ``` is equal to ```C++ template void foo(const T& val); ```
```C++ auto&& val = 5; ``` is equal to ```C++ template void foo(T&& val); ```
___ ## auto deduction - one exception ```C++ template void foo(T t) {} auto val = {1, 2, 3, 4}; // std::initializer_list foo({1, 2, 3, 4}); // deduction failed! ``` Need to explicity use `initializer_list` ```C++ template void foo(std::initializer_list t) {} auto val = {1, 2, 3, 4}; // std::initializer_list foo({1, 2, 3, 4}); // std::initializer_list ``` ___ ## 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.
```C++ auto lambda = [](auto&& first, const auto& second, auto third) {} ``` is equal to ```C++ struct Lmabda { template auto operator()(X&& x, const Y& y, Z z) const { } }; ```
___ ## std::forward once more If we want to perfect forward some value in template you will write: ```C++ template void fun(T&& t) { other(std::forward(t)); } ``` But how to do this in lambda? We know that generic lambda is a teplate, but we don't have an access to `T`! ```C++ auto lambda = [](auto&& t) { other(std::forward(t)); }; ``` ___ ## 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 vec{1}; decltype(vec.begin()) it; // std::vector::iterator decltype(vec[0]) // int& ``` ___ ## decltype - one problem What is wrong with this snippet of code? ```C++ void authorize() {} template auto authorizeAndAccess(C& container, size_t index) { authorize(); return container[index]; } int main() { std::vector vec{1,2,3}; authorizeAndAccess(vec, 2) = 10; std::cout << vec[2] << '\n'; } ``` ```C++ error: lvalue required as left operand of assignment authorizeAndAccess(vec, 2) = 10; ``` ___ ## decltype - partial solution The same result we can achieve by using `decltype(auto)`. ```C++ void authorize() {} template auto authorizeAndAccess(C& container, size_t index) -> decltype(container[index]) { authorize(); return container[index]; } int main() { std::vector vec{1,2,3}; authorizeAndAccess(vec, 2) = 10; std::cout << vec[2] << '\n'; } ``` ___ ## decltype - when solution make another trouble What is wrong now? ```C++ void authorize() {} template decltype(auto) authorizeAndAccess(C& container, size_t index) { authorize(); return container[index]; } int main() { const auto res = authorizeAndAccess(std::vector{5, 8, 12, 16}, 2); std::cout << std::boolalpha << "res: " << res << '\n'; } ``` ```C++ cannot bind non-const lvalue reference of type ‘std::vector&’ to an rvalue of type ‘std::vector’ const auto res = authorizeAndAccess(std::vector{5, 8, 12, 16}, 2); ``` ___ ## decltype - final fix ```C++ void authorize() {} template decltype(auto) authorizeAndAccess(C&& container, size_t index) { authorize(); return std::forward(container)[index]; } int main() { const auto res = authorizeAndAccess(std::vector{5, 8, 12, 16}, 2); std::cout << std::boolalpha << "res: " << res << '\n'; // will print 12 } ```