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的parameterBy-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_listbut 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
}
autoin a function return type or a lambda parameter implies template type deduction, notautotype 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();
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!
