10 KiB
Deduction guides
Template deduction
There are three different scenarios during template type deduction:
-
Handle by reference or pointer:
T&orT*with or withoutconstorvolatile -
Handle by value:
Twith or withoutconstorvolatile -
Handle by universal reference:
T&&can't useconstorvolatilehere.
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.
auto val = 5;
is equal to
template <typename T>
void foo(T val);
const auto& val = 5;
is equal to
template <typename T>
void foo(const T& val);
auto&& val = 5;
is equal to
template <typename T>
void foo(T&& val);
auto deduction - one exception
template <typename T>
void foo(T t) {}
auto val = {1, 2, 3, 4}; // std::initializer_list<int>
foo({1, 2, 3, 4}); // deduction failed!
Need to explicity use initializer_list
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>
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.
auto lambda = [](auto&& first, const auto& second, auto third) {}
is equal to
struct Lmabda {
template <typename X, typename Y, typename Z>
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:
template <typename T>
void fun(T&& t) {
other(std::forward<T>(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!
auto lambda = [](auto&& t) {
other(std::forward<decltype(t)>(t));
};
decltype
Decltype return a type of variable, without removing references or const/ volatile qualifiers
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&
decltype - one problem
What is wrong with this snippet of code?
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';
}
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).
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';
}
decltype - when solution make another trouble
What is wrong now?
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';
}
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);
decltype - final fix
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
}