Contents

Effective Modern C++ Notes - Deducing Types

記錄Effective Modern C++主要概念和容易忘記的重點

Updated on 2021-05-08
  • 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
  • General rule: If you can take its address, it’s an lvalue
  • Conceptual motivation: Lvalues may not be moved from
  • Conceptually: temporary objects. e.g., by-value function return
  • Conceptual motivation: Rvalues may be moved from

General problem:

1
2
3
4
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.

  • If expr’s type is a reference, ignore that (T的&會被去掉)
  • Pattern-match expr’s type against ParamType to determine T
  • 如果expr是Lvalue,T和param都會是Lvalue reference
  • 如果expr是Rvalue,T會是原本的type,param是Rvalue reference

在C++ 14可以使用auto&&來當作lambda function的parameter

  • 如果expr是pointer以外的type,可視為copy,去掉&constvolatile
  • 如果expr是pointer type,T和ParamType的type跟expr一樣
  • In C/C++, type of array and pointer is same as a function parameter.

void f(int param[]); is same as void f(int *param);

  • In C/C++, function types can decay into function pointer
  • expr在Pointer、Function pointer,推導出來的param不一樣
expr T param
int* T int*
int(*)() T int(*)()
int* T* int*
int(*)() T* int(*)()
int* T& int*&
int(*)() T& int(&)()
int* T&& int*&
int(*)() T&& int(&)()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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)
  • 神奇的範例
1
2
3
4
5
6
7
8
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?

  • Same as template type deduction, except with braced initializers
    • auto deduces std::initializer_list but template don’t

braced initializer don’t have a type, 只有auto有特殊規則

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#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, not auto type deduction
1
2
3
4
auto createInitList()
{
	return {1, 2, 3};  // Error: can't deduce type
}
  • decltype(name) ≡ declared type of name
1
2
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:
1
2
3
4
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而定
1
2
3
4
5
6
7
8
9
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&
  • 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()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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)的規則也可以來宣告變數
1
2
3
4
5
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&
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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&
}
  • IDE, std::type_info::name可能會有問題,Compiler Diagnostics和boost::typeindex是比較可靠的,重點是自己要弄懂…
1
2
3
4
5
template<typename T>
class TD;

int i = 0;
TD<decltype(i)> iType;   // compiler will show error message with i's type
  • std::type_info::name
1
2
typeid(T).name();
typeid(param).name();
  • boost::typeindex
1
2
3
4
using boost::typeindex::type_id_with_cvr;

type_id_with_cvr<T>().pretty_name();
type_id_with_cvr<decltype>().pretty_name();





Комментарии