The explicitly typed initializer idiom involves declaring a variable with auto
, but casting the initialization expression to the type you want auto to deduce. Here's how it can be used to force highPriority
to be a bool
, for example:
autohighPriority = static_cast(features(w)[5] );
Here, features(w)[5]
continues to return a std::vector::reference
object, just as it always has, but the cast changes the type of the expression to bool
, which auto
then deduces as the type for highPriority
. At runtime, the std::vector::reference
object returned from std::vector::operator[]
executes the conversion to bool
that it supports, and as part of that conversion, the still-valid pointer to the std::vector
returned from features is dereferenced. That avoids the undefined behavior we ran into earlier. The index 5 is then applied to the bits pointed to by the pointer, and the bool
value that emerges is used to initialize highPriority
.
For the Matrix
example, the explicitly typed initializer idiom would look like this:
autosum = static_cast(m1 + m2 + m3 + m4 );
Applications of the idiom aren't limited to initializers yielding proxy class types. It can also be useful to emphasize that you are deliberately creating a variable of a type that is different from that generated by the initializing expression. For example, suppose you have a function to calculate some tolerance value:
double calcEpsilon(); // return tolerance value
calcEpsilon
clearly returns a double
, but suppose you know that for your application, the precision of a float
is adequate, and you care about the difference in size between float
s and double
s. You could declare a float variable to store the result of calcEpsilon,
floatep = calcEpsilon(); // impliclitly convert
// double → float
but this hardly announces “I'm deliberately reducing the precision of the value returned by the function.” A declaration using the explicitly typed initializer idiom, however, does:
autoep = static_cast(calcEpsilon() );
Similar reasoning applies if you have a floating-point expression that you are deliberately storing as an integral value. Suppose you need to calculate the index of an element in a container with random access iterators (e.g., a std::vector
, std::deque
, or std::array
), and you're given a double
between 0.0
and 1.0
indicating how far from the beginning of the container the desired element is located. ( 0.5
would indicate the middle of the container.) Further suppose that you're confident that the resulting index will fit in an int
. If the container is c and the double is d
, you could calculate the index this way,
intindex = d * c.size();
but this obscures the fact that you're intentionally converting the double
on the right to an int
. The explicitly typed initializer idiom makes things transparent:
autoindex = static_cast(d * c.size() );
Things to Remember
• “Invisible” proxy types can cause auto
to deduce the “wrong” type for an initializing expression.
• The explicitly typed initializer idiom forces auto
to deduce the type you want it to have.
Chapter 3
Moving to Modern C++
When it comes to big-name features, C++11 and C++14 have a lot to boast of. auto
, smart pointers, move semantics, lambdas, concurrency — each is so important, I devote a chapter to it. It's essential to master those features, but becoming an effective modern C++ programmer requires a series of smaller steps, too. Each step answers specific questions that arise during the journey from C++98 to modern C++. When should you use braces instead of parentheses for object creation? Why are alias declarations better than typedef
s? How does constexpr
differ from const
? What's the relationship between const
member functions and thread safety? The list goes on and on. And one by one, this chapter provides the answers.
Item 7: Distinguish between ()
and {}
when creating objects.
Depending on your perspective, syntax choices for object initialization in C++11 embody either an embarrassment of riches or a confusing mess. As a general rule, initialization values may be specified with parentheses, an equals sign, or braces:
int x (0 ); // initializer is in parentheses
int y =0; // initializer follows "="
int z {0 }; // initializer is in braces
In many cases, it's also possible to use an equals sign and braces together:
int z = {0 }; // initializer uses "=" and braces
For the remainder of this Item, I'll generally ignore the equals-sign-plus-braces syntax, because C++ usually treats it the same as the braces-only version.
The “confusing mess” lobby points out that the use of an equals sign for initialization often misleads C++ newbies into thinking that an assignment is taking place, even though it's not. For built-in types like int
, the difference is academic, but for user-defined types, it's important to distinguish initialization from assignment, because different function calls are involved:
Widget w1; // call default constructor
Widget w2 = w1; // not an assignment; calls copy ctor
w1 = w2; // an assignment; calls copy operator=
Even with several initialization syntaxes, there were some situations where C++98 had no way to express a desired initialization. For example, it wasn't possible to directly indicate that an STL container should be created holding a particular set of values (e.g., 1, 3, and 5).
To address the confusion of multiple initialization syntaxes, as well as the fact that they don't cover all initialization scenarios, C++11 introduces uniform initialization : a single initialization syntax that can, at least in concept, be used anywhere and express everything. It's based on braces, and for that reason I prefer the term braced initialization . “Uniform initialization” is an idea. “Braced initialization” is a syntactic construct.
Braced initialization lets you express the formerly inexpressible. Using braces, specifying the initial contents of a container is easy:
std::vector v {1, 3, 5 }; // v's initial content is 1, 3, 5
Braces can also be used to specify default initialization values for non-static data members. This capability — new to C++11 — is shared with the “=” initialization syntax, but not with parentheses:
class Widget {
…
private:
int x {0 }; // fine, x's default value is 0
int y =0; // also fine
int z(0); // error!
};
On the other hand, uncopyable objects (e.g., std::atomic
s — see Item 40) may be initialized using braces or parentheses, but not using “=”:
Читать дальше