Effective Modern C++ Notes - Deducing Types

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

Blog上同步刊登

  • 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,去掉&constvolatile
  • 如果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 as void 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, not auto 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();


CC BY-NC-ND 2.0 授权

喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!

tratitudeWelcome to my blog: https://tratitude-blog.netlify.app/
  • 来自作者

Title