欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Effective Modern C++

程序员文章站 2024-02-29 08:12:04
...

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.