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 // see info
constexpr std::size_t arraySize( T (&)[N]) noexcept // below on
{ // constexpr
return N; // and
} // noexcept
As Item 15explains, declaring this function constexpr makes its result available during compilation. That makes it possible to declare, say, an array with the same number of elements as a second array whose size is computed from a braced initializer:
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::arrayarraySize(keyVals)> mappedVals; // mappedVals'
// size is 7
As for arraySize
being declared noexcept
, that's to help compilers generate better code. For details, see Item 14.
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
void f1(T param); // in f1, param passed by value
template
void f2(T& param); // in f2, param passed by ref
fl(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)
This rarely makes any difference in practice, but if you're going to know about array- to-pointer decay, you might as well know about function-to-pointer decay, too.
So there you have it: the auto
-related rules for template type deduction. I remarked at the outset that they're pretty straightforward, and for the most part, they are. The special treatment accorded lvalues when deducing types for universal references muddies the water a bit, however, and the decay-to-pointer rules for arrays and functions stirs up even greater turbidity. Sometimes you simply want to grab your compilers and demand, “Tell me what type you're deducing!” When that happens, turn to Item 4
, because it's devoted to coaxing compilers into doing just that.
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.
Item 2: Understand auto
type deduction.
If you've read Item 1on template type deduction, you already know almost everything you need to know about auto
type deduction, because, with only one curious exception, auto
type deduction is template type deduction. But how can that be? Template type deduction involves templates and functions and parameters, but auto
deals with none of those things.
That's true, but it doesn't matter. There's a direct mapping between template type deduction and auto
type deduction. There is literally an algorithmic transformation from one to the other.
In Item 1, template type deduction is explained using this general function template
templateT>
void f( ParamType param);
and this general call:
f( expr ); // call f with some expression
In the call to f, compilers use expr
to deduce types for T and ParamType
.
When a variable is declared using auto
, auto
plays the role of T in the template, and the type specifier for the variable acts as ParamType
. This is easier to show than to describe, so consider this example:
autox = 27;
Here, the type specifier for x
is simply auto
by itself. On the other hand, in this declaration,
const autocx = x;
the type specifier is const auto
. And here,
const auto&rx = x;
the type specifier is const auto&
. To deduce types for x
, cx
, and rx
in these examples, compilers act as if there were a template for each declaration as well as a call to that template with the corresponding initializing expression:
template // conceptual template for
void func_for_x( Tparam); // deducing x's type
func_for_x(27); // conceptual call: param's
// deduced type is x's type
template // conceptual template for
void func_for_cx( const Tparam); // deducing cx's type
func_for_cx(x); // conceptual call: param's
// deduced type is cx's type
template // conceptual template for
void func_for_rx( const T¶m); // deducing rx's type
func_for_rx(x); // conceptual call: param's
// deduced type is rx's type
As I said, deducing types for auto is, with only one exception (which we'll discuss soon), the same as deducing types for templates.
Item 1divides template type deduction into three cases, based on the characteristics of ParamType
, the type specifier for param
in the general function template. In a variable declaration using auto
, the type specifier takes the place of ParamType
, so there are three cases for that, too:
• Case 1: The type specifier is a pointer or reference, but not a universal reference.
• Case 2: The type specifier is a universal reference.
• Case 3: The type specifier is neither a pointer nor a reference.
We've already seen examples of cases 1 and 3:
autox = 27; // case 3 (x is neither ptr nor reference)
const autocx = x; // case 3 (cx isn't either)
const auto&rx = x; // case 1 (rx is a non-universal ref.)
Case 2 works as you'd expect:
auto&&uref1 = x; // x is int and lvalue,
// so uref1's type is int&
auto&&uref2 = cx; // cx is const int and lvalue,
// so uref2's type is const int&
Читать дальше