Effective Modern C++
Item 1: Understand template type deduction
template<typename T>
void f(ParamType param);
f(expr); // call f with some expression
The type deduced for T is dependent not just on the type of expr, but also on the form of ParamType. There are three cases:
- ParamType is a pointer or reference type, but not a universal reference.
- ParamType is a universal reference.
- ParamType is neither a pointer nor a reference.
CASE 1: ParamType is a pointer or reference type, but not a universal reference
- If expr’s type is a reference, ignore the reference part.
- Then pattern-match expr’s type against ParamType to determine T.
template<typename T>
void f(T& param); // param is a reference
int x = 27; // x is an int
const int cx = x; // cx is a const int
const int& rx = x; // rx is a reference to x as a const int
f(x); // T is int, param's type is int&
f(cx); // T is const int, param's type is const int&
f(rx); // T is const int, param's type is const int&
These examples all show lvalue reference parameters, but type deduction works exactly the same way for rvalue reference parameters.
template<typename T>
void f(const T& param); // param is now a ref-to-const
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T is int, param's type is const int&
f(cx); // T is int, param's type is const int&
f(rx); // T is int, param's type is const int&
CASE 2: ParamType is a Universal Reference
- If expr is an lvalue, both T and ParamType are deduced to be lvalue references.
That’s doubly unusual. First, it’s the only situation in template type deduction where T is deduced to be a reference. Second, although ParamType is declared using the syntax for an rvalue reference, its deduced type is an lvalue reference. - If expr is an rvalue, the “normal” (i.e., Case 1) rules apply.
template<typename T>
void f(T&& param); // param is now a universal reference
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(rx); // rx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is rvalue, so T is int, param's type is therefore int&&
CASE 3: ParamType is neither a pointer nor a reference
When ParamType is neither a pointer nor a reference, we’re dealing with pass-byvalue. That means that param will be a copy of whatever is passed in—a completely new object.
- As before, if expr’s type is a reference, ignore the reference part.
- If, after ignoring expr’s reference-ness, expr is const, ignore that, too. If it’s volatile, also ignore that. (volatile objects are uncommon. They’re generally used only for implementing device drivers. For details, see Item 40.)
template<typename T>
void f(T param); // param is now passed by value
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int
Note that even though cx and rx represent const values, param isn’t const. That makes sense. param is an object that’s completely independent of cx and rx—a copy of cx or rx.
Const (and volatile) is ignored only for by-value parameters, for parameters that are references-to- or pointers-toconst, the constness of expr is preserved during type deduction.
template<typename T>
void f(T param); // param is still passed by value
const char* const ptr = "Fun with pointers"; // ptr is const pointer to const object
f(ptr); // pass arg of type const char * const
The type deduced for param will be const char*.
Array Arguments
In many contexts, an array decays into a pointer to its first element.
template<typename T>
void f(T param); // template with by-value parameter
const char name[] = "J. P. Briggs"; // name's type is const char[13]
const char * ptrToName = name; // array decays to pointer
f(name); // name is array, but T deduced as const char*
Because array parameter declarations are treated as if they were pointer parameters, the type of an array that’s passed to a template function by value is deduced to be a pointer type. That means that in the call to the template f, its type parameter T is deduced to be const char*.
template<typename T>
void f(T& param); // template with by-reference parameter
f(name); // T is const char [13], param is const char (&)[13].
Interestingly, the ability to declare references to arrays enables creation of a template that deduces the number of elements that an array contains:
// return size of an array as a compile-time constant. (The
// array parameter has no name, because we care only about
// the number of elements it contains.)
template<typename T, std::size_t N> // see info below on constexpr and noexcept
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has 7 elements
int mappedVals[arraySize(keyVals)]; // so does mappedVals
Of course, as a modern C++ developer, you’d naturally prefer a std::array to a built-in array:
std::array<int, arraySize(keyVals)> mappedVals; // mappedVals' size is 7
As for arraySize being declared noexcept, that’s to help compilers generate better code.
Function Arguments
Arrays aren’t the only things in C++ that can decay into pointers. Function types can decay into function pointers, and everything we’ve discussed regarding type deduction for arrays applies to type deduction for functions and their decay into function pointers. As a result
void someFunc(int, double); // someFunc is a function; type is void(int, double)
template<typename T>
void f1(T param); // in f1, param passed by value
template<typename T>
void f2(T& param); // in f2, param passed by ref
f1(someFunc); // param deduced as ptr-to-func; type is void (*)(int, double)
f2(someFunc); // param deduced as ref-to-func; type is void (&)(int, double)
Things to Remember
During template type deduction, arguments that are references are treated as non-references, i.e., their reference-ness is ignored.
When deducing types for universal reference parameters, lvalue arguments get special treatment.
When deducing types for by-value parameters, const and/or volatile arguments are treated as non-const and non-volatile.
During template type deduction, arguments that are array or function names decay to pointers, unless they’re used to initialize references.
上一篇: 用Kruskal算法求最小生成树
下一篇: java直接插入排序示例
推荐阅读
-
Effective Modern C++
-
leetcode刷题【简单】删除排序链表中的重复元素 c++
-
modern effective C++ 18
-
Exercise Of Modern Compiler Implementation in C
-
C++备忘录060:noexcept gist of "Effective Modern C++"
-
effective c++条款17:以独立语句将newed对象置入智能指针
-
java及C++中传值传递、引用传递和指针方式的理解
-
Java中对象与C++中对象的放置安排的对比
-
c++ -> 运算符重载(+ && -)
-
c/c++实现奇偶数,质数,最大公约数,最小公倍数,最大奇约数判断