Following the C++ Standard:
§ 8.5 Initializers [dcl.init]
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
I can think of the example given in the book:
auto x = features(w)[5];
as the one that represents any form of copy-initialization with auto / template type (deduced type in general), just like:
template <typename A>
void foo(A x) {}
foo(features(w)[5]);
as well as:
auto bar()
{
return features(w)[5];
}
as well as:
auto lambda = [] (auto x) {};
lambda(features(w)[5]);
So the point is, we cannot always just "move type T from static_cast<T>
to the left-hand side of assignment".
Instead, in any of the above examples we need to explicitly specify the desired type rather than allowing compiler deduce one on its own, if the latter can lead to undefined behavior:
Respectively to my examples that would be:
/*1*/ foo(static_cast<bool>(features(w)[5]));
/*2*/ return static_cast<bool>(features(w)[5]);
/*3*/ lambda(static_cast<bool>(features(w)[5]));
As such, using static_cast<T>
is an elegant way of forcing a desired type, which alternatively can be expressed by explicit contructor call:
foo(bool{features(w)[5]});
To summarize, I don't think the book says:
Whenever you want to force the type of a variable, use auto x = static_cast<T>(y);
instead of T x{y};
.
To me it sounds more like a word of warning:
The type inference with auto
is cool, but may end up with undefined behavior if used unwisely.
And as a solution for the scenarios involving type deduction, the following is proposed:
If the compiler's regular type-deduction mechanism is not what you want, use static_cast<T>(y)
.
UPDATE
And answering your updated question, which of the below initializations should one prefer:
bool priority = features(w)[5];
auto priority = static_cast<bool>(features(w)[5]);
auto priority = bool(features(w)[5]);
auto priority = bool{features(w)[5]};
Scenario 1
First, imagine the std::vector<bool>::reference
is not implicitly convertible to bool
:
struct BoolReference
{
explicit operator bool() { /*...*/ }
};
Now, the bool priority = features(w)[5];
will not compile, as it is not an explicit boolean context. The others will work fine (as long as the operator bool()
is accessible).
Scenario 2
Secondly, let's assume the std::vector<bool>::reference
is implemented in an old fashion, and although the conversion operator is not explicit
, it returns int
instead:
struct BoolReference
{
operator int() { /*...*/ }
};
The change in signature turns off the auto priority = bool{features(w)[5]};
initialization, as using {}
prevents the narrowing (which converting an int
to bool
is).
Scenario 3
Thirdly, what if we were talking not about bool
at all, but about some user-defined type, that, to our surprise, declares explicit
constructor:
struct MyBool
{
explicit MyBool(bool b) {}
};
Surprisingly, once again the MyBool priority = features(w)[5];
initialization will not compile, as the copy-initialization syntax requires non-explicit constructor. Others will work though.
Personal attitude
If I were to choose one initialization from the listed four candidates, I would go with:
auto priority = bool{features(w)[5]};
because it introduces an explicit boolean context (which is fine in case we want to assign this value to boolean variable) and prevents narrowing (in case of other types, not-easily-convertible-to-bool), so that when an error/warning is triggered, we can diagnose what features(w)[5]
really is.
UPDATE 2
I have recently watched Herb Sutter's speech from CppCon 2014 titled Back to the Basics! Essentials of Modern C++ Style, where he presents some points on why should one prefer the explicit type initializer of auto x = T{y};
form (though it is not the same as with auto x = static_cast<T>(y)
, so not all arguments apply) over T x{y};
, which are:
auto
variables must always be initialized. That is, you can't write auto a;
, just like you can write error-prone int a;
The modern C++ style prefers the type on the right side, just like in:
a) Literals:
auto f = 3.14f;
// ^ float
b) User-defined literals:
auto s = "foo"s;
// ^ std::string
c) Function declarations:
auto func(double) -> int;
d) Named lambdas:
auto func = [=] (double) {};
e) Aliases:
using dict = set<string>;
f) Template aliases:
template <class T>
using myvec = vector<T, myalloc>;
so as such, adding one more:
auto x = T{y};
is consistent with the style where we have name on the left side, and type with initializer on the right side, what can be briefly described as:
<category> name = <type> <initializer>;
With copy-elision and non-explicit copy/move constructors it has zero-cost compared to T x{y}
syntax.
It is more explicit when there are subtle differences between the types:
unique_ptr<Base> p = make_unique<Derived>(); // subtle difference
auto p = unique_ptr<Base>{make_unique<Derived>()}; // explicit and clear
{}
guarantees no implicit conversions and no narrowing.
But he also mentions some drawbacks of the auto x = T{}
form in general, which has already been described in this post:
Even though the compiler can elide the right-hand side's temporary, it requires an accessible, non-deleted and non-explicit copy-constructor:
auto x = std::atomic<int>{}; // fails to compile, copy constructor deleted
If the elision is not enabled (e.g. -fno-elide-constructors
), then moving non-movable types results in expensive copy:
auto a = std::array<int,50>{};
static_cast
rather than in place ofauto
– Denverdenystatic_cast
is explained in the comment (i.e., to avoid UB). I think that what the author is trying to say is to use his solution only to specific situations as the illustrated example. There's no need to use it when use ofauto
doesn't cause any implications. – Claspauto x = static_cast<Y>(z);
for what can be expressed asY x = z;
– DenverdenyY x=e;
is better becauseauto x=static_cast<Y>(e);
activatesexplicit
conversions, which should be used with caution. I suspect annotation saying the return value should not be persisted past the invoked line might be in order for C++. – Selfwinding