Effective Modern C++ Notes - Deducing Types
IPFS
- Lecture: Scott Meyers - Effective Modern C++
- Distinguish lvalues and rvalues
- Understand type deduction
- Understand std::move and std::forward
- Prefer auto to explicit types when declaring objects
- Remember that auto + {expr} => std::initializer_list
- Distinguish universal references from rvalue references
- Pass and return rvalue references via std::move, universal references via std::forward
- Understand reference collapsing
- Assume that move operations are not present, not cheap, and not used
- Avoid default capture modes
- Make const member functions thread-safe
- Book: Effective Modern C++ Chapter 1
Distinguish lvalues and rvalues
Lvalues
- General rule: If you can take its address, it’s an lvalue
- Conceptual motivation: Lvalues may not be moved from
Rvalues
- Conceptually: temporary objects. e.g., by-value function return
- Conceptual motivation: Rvalues may be moved from
Understand type deduction
Template type deduction
General problem:
template<typename T> void f(ParamType param); f(expr); // deduce T and ParamType from expr
- Given type of expr, what are the type of T and ParamType?
- Three general cases:ParamType is a reference or pointer
- ParamType is a universal reference
- ParamType is neither reference nor pointer (By-Value Parameters)
T和auto
的推導規則一樣
class template don’t have class deduction, only function template have deduction.
ParamType is a reference or pointer
- If expr’s type is a reference, ignore that (T的&會被去掉)
- Pattern-match expr’s type against ParamType to determine T
ParamType is a universal reference
- 如果expr是Lvalue,T和param都會是Lvalue reference
- 如果expr是Rvalue,T會是原本的type,param是Rvalue reference
在C++ 14可以使用auto&&
來當作lambda function的parameter
By-Value Parameters
- 如果expr是pointer以外的type,可視為copy,去掉
&
、const
、volatile
- 如果expr是pointer type,T和ParamType的type跟expr一樣
Array Arguments & Function Arguments
- In C/C++, type of array and pointer is same as a function parameter.
void f(int param[]);
is same asvoid f(int *param);
- In C/C++, function types can decay into function pointer
- expr在Pointer、Function pointer,推導出來的param不一樣
const int i = 0; const int& ri = i; const char name[] = "name"; // type is const char [5] const char* ptrToName = name; void someFunc(int, double); // type is void(int, double) template<typename T> void f1(T param); f1(i); // T and ParamType are int f1(ri); // T and ParamType are int f1(name); // T and ParamType are const char* f1(ptrToName); // T and ParamType are const char* f1(10); // T and ParamType are int template<typename T> void f2(T* param); f2(i); // T and ParamType are const int* f2(ri); // Error: not match f2(name); // T and ParamType are const char* f2(ptrToName); // T and ParamType are const char* f2(10); // Error: not match template<typename T> void f3(T& param); f3(i); // T is const int, ParamType is const int& f3(ri); // T is const int, ParamType is const int& f3(name); // T is const char[5], ParamType is const char (&)[5] f3(ptrToName); // T is const char*, ParamType is const char*& f3(10); // Error: 10 is Rvalue template<typename T> void f4(T&& param); f4(i); // T and ParamType are const int& f4(ri); // T and ParamType are const int& f4(name); // T and ParamType are const char (&)[5] f4(ptrToName); // T and ParamType are const char*& f4(10); // T is int, ParamType is int&& f1(someFunc); // ParamType is void(*)(int, double) f2(someFunc); // ParamType is void(*)(int, double) f3(someFunc); // ParamType is void(&)(int, double) f4(someFunc); // ParamType is void(&)(int, double)
- 神奇的範例
template<typename T, std::size_t N> constexpr std::size_t arraySize(T (&)[N]) noexcept { return N; } int val[] = {1, 2, 3}; std::array<int , arraySize(val)> arr; // arraySize(val) => 3
Q: 甚麼時候parameter可以不需要name?
auto Type Deduction
- Same as template type deduction, except with braced initializersauto deduces
std::initializer_list
but template don’t
braced initializer don’t have a type, 只有auto有特殊規則
#include <initializer_list> template<typename T> void f(T param){} int main() { auto d = {1, 2}; // OK: type of d is std::initializer_list<int> auto n = {5}; // OK: type of n is std::initializer_list<int> // auto e{1, 2}; // Error as of DR n3922, std::initializer_list<int> before auto m{5}; // OK: type of m is int as of DR n3922, initializer_list<int> before f(d); // OK: d have a type // f({1, 2}); // Error: deduction only work on auto, {1, 2} have no type }
auto
in a function return type or a lambda parameter implies template type deduction, notauto
type deduction
auto createInitList() { return {1, 2, 3}; // Error: can't deduce type }
decltype Type Deduction
- decltype(name) ≡ declared type of name
int x = 10; // decltype(x) ≡ int const auto& rx = x; // decltype(rx) ≡ const int&
- dectype(Lvalue expr of type T) ≡ T&
- Names are lvalues, but decltype(name) rule beat decltype(expr) rule:
int x; decltype(x) ≡ int // x is lvalue expression, but also a name // => name rule prevails decltype((x)) ≡ int& // (x) is lvalue expression, but isn't a name
operator[]
回傳的type要根據container而定
template<typename T> class deque { //... T& operator[](std::size_t index); //... }; deque<int> d; // decltype(d) ≡ deque<int> d[0] = 1; // decltype(d[0]) ≡ int&
Function return type
- 把
auto
放在function return type,參考access1()、 access2() - 加上
decltype
規則,decltype(auto)
跟回傳值的type一樣,參考access3() - 讓parameter
c
能夠接受Rvalue和Lvalue,c
要使用Universal reference type。如果c
是Rvalue,會在function結束後消失,回傳值會發生問題,所以return時要用forward
,參考access4()、access5()
// In C++ 11 template<typename Container, typename Index> auto access1(Container& c, Index i) -> decltype(c[i]) { return c[i]; } // In C++ 14 template<typename Container, typename Index> auto access2(Container& c, Index i) { return c[i]; } // In C++ 14 template<typename Container, typename Index> decltype(auto) access3(Container& c, Index i) { return c[i]; } // In C++ 11 template<typename Container, typename Index> auto access4(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i]) { return std::forward<Container>(c)[i]; } // In C++ 14 template<typename Container, typename Index> decltype(auto) access5(Container&& c, Index i) { return std::forward<Container>(c)[i]; } std::deque<int> d = {1, 2, 3}; // decltype(d[0]) ≡ int& access1(d, 1) = 10; // Ok access2(d, 1) = 10; // Compile Error: the type of d[5] is int& // but the deduction type of auto is int // (Use deduction rule No.3, By-Value Parameters) access3(d, 1) = 10; // Ok auto d1 = access4(std::move(d), 1); // Ok, same as access5()
decltype(auto)
的規則也可以來宣告變數
int i; const int& ci = i; auto a = cw; // auto type deduction, decltype(a) is int decltype(auto) b = cw; // decltype type deduction, decltype(b) is const int&
- 小心return a reference of local variable,因為decltype(Lvalue expr of type T) ≡ T&
decltype(auto) f1() { int x = 0; return x; // Ok: decltype(x) is int, so f1 return int } decltype(auto) f2 { int x = 0; return (x); // Error: decltype((x)) is int&, so f1 return int& }
How to view deduced types
- IDE,
std::type_info::name
可能會有問題,Compiler Diagnostics和boost::typeindex
是比較可靠的,重點是自己要弄懂…
Compiler Diagnostics
template<typename T> class TD; int i = 0; TD<decltype(i)> iType; // compiler will show error message with i's type
Runtime Output
std::type_info::name
typeid(T).name(); typeid(param).name();
boost::typeindex
using boost::typeindex::type_id_with_cvr; type_id_with_cvr<T>().pretty_name(); type_id_with_cvr<decltype>().pretty_name();
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!