Следуя стандарту С++:
§ 8.5 Инициализаторы [dcl.init]
Инициализация, которая происходит в форме
T x = a;
а также при передаче аргументов, возврате функции, создании исключения (15.1), обработке исключения (15.3) и инициализации агрегатного члена (8.5.1) называется инициализация копированием.
Я могу вспомнить пример, приведенный в книге:
auto x = features(w)[5];
как тот, который представляет любую форму инициализации копирования с типом auto/template (в общем случае выведенный тип), точно так же, как:
template <typename A>
void foo(A x) {}
foo(features(w)[5]);
так же как:
auto bar()
{
return features(w)[5];
}
так же как:
auto lambda = [] (auto x) {};
lambda(features(w)[5]);
Итак, дело в том, что мы не всегда можем просто "переместить тип T из static_cast<T>
в левую часть присваивания".
Вместо этого в любом из приведенных выше примеров нам нужно явно указать желаемый тип, а не позволять компилятору выводить его самостоятельно, если последнее может привести к неопределенному поведению:
Соответственно моим примерам это будет:
/*1*/ foo(static_cast<bool>(features(w)[5]));
/*2*/ return static_cast<bool>(features(w)[5]);
/*3*/ lambda(static_cast<bool>(features(w)[5]));
Таким образом, использование static_cast<T>
— это элегантный способ форсирования желаемого типа, который в качестве альтернативы может быть выражен явным вызовом конструктора:
foo(bool{features(w)[5]});
Подводя итог, я не думаю, что в книге говорится:
Всякий раз, когда вы хотите принудительно указать тип переменной, используйте auto x = static_cast<T>(y);
вместо T x{y};
.
Для меня это больше похоже на предупреждение:
Вывод типа с auto
— это круто, но может привести к неопределенному поведению, если использовать его неразумно.
В качестве решения для сценариев, включающих вывод типа, предлагается следующее:
Если обычный механизм вывода типов компилятора вам не подходит, используйте static_cast<T>(y)
.
ОБНОВЛЕНИЕ
И отвечая на ваш обновленный вопрос, какую из следующих инициализаций следует предпочесть:
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]};
Сценарий 1
Во-первых, представьте, что std::vector<bool>::reference
неявно преобразуется в bool
:
struct BoolReference
{
explicit operator bool() { /*...*/ }
};
Теперь bool priority = features(w)[5];
не будет компилироваться, так как это не явный логический контекст. Остальные будут работать нормально (пока operator bool()
доступен).
Сценарий 2
Во-вторых, давайте предположим, что std::vector<bool>::reference
реализован по старинке, и хотя оператор преобразования не является explicit
, вместо этого он возвращает int
:
struct BoolReference
{
operator int() { /*...*/ }
};
Изменение подписи отключает инициализацию auto priority = bool{features(w)[5]};
, так как использование {}
предотвращает сужение (которым является преобразование int
в bool
).
Сценарий 3
В-третьих, что, если бы мы говорили вовсе не о bool
, а о каком-то пользовательском типе, который, к нашему удивлению, объявляет конструктор explicit
:
struct MyBool
{
explicit MyBool(bool b) {}
};
Удивительно, но снова инициализация MyBool priority = features(w)[5];
не скомпилируется, поскольку синтаксис инициализации копирования требует неявного конструктора. Хотя другие будут работать.
Личное отношение
Если бы мне нужно было выбрать одну инициализацию из перечисленных четырех кандидатов, я бы выбрал:
auto priority = bool{features(w)[5]};
потому что он вводит явный логический контекст (что хорошо, если мы хотим присвоить это значение логической переменной) и предотвращает сужение (в случае других типов, не-легко-преобразуемых-в-bool), так что при ошибке/ предупреждение срабатывает, мы можем диагностировать, что features(w)[5]
на самом деле.
ОБНОВЛЕНИЕ 2
Недавно я просмотрел выступление Херба Саттера на CppCon 2014 под названием Назад к основы! Essentials of Modern C++ Style, где он рассказывает о том, почему следует предпочесть явный инициализатор типа в форме auto x = T{y};
(хотя это не то же самое, что и в auto x = static_cast<T>(y)
, поэтому применимы не все аргументы) над T x{y};
, а именно:
auto
переменные всегда должны быть инициализированы. То есть вы не можете написать auto a;
, так же как можете написать подверженное ошибкам int a;
В современном стиле C++ предпочтение отдается шрифту с правой стороны, как в следующем примере:
а) Литералы:
auto f = 3.14f;
// ^ float
б) Определенные пользователем литералы:
auto s = "foo"s;
// ^ std::string
в) Объявления функций:
auto func(double) -> int;
г) Именованные лямбды:
auto func = [=] (double) {};
д) Псевдонимы:
using dict = set<string>;
f) Псевдонимы шаблонов:
template <class T>
using myvec = vector<T, myalloc>;
как таковой, добавив еще один:
auto x = T{y};
соответствует стилю, в котором имя находится слева, а тип с инициализатором справа, что можно кратко описать так:
<category> name = <type> <initializer>;
С конструкторами копирования-исключения и неявными конструкторами копирования/перемещения он имеет нулевую стоимость по сравнению с синтаксисом T x{y}
.
Это более явно, когда между типами есть тонкие различия:
unique_ptr<Base> p = make_unique<Derived>(); // subtle difference
auto p = unique_ptr<Base>{make_unique<Derived>()}; // explicit and clear
{}
гарантирует отсутствие неявных преобразований и сужений.
Но он также упоминает некоторые недостатки формы auto x = T{}
в целом, которые уже были описаны в этом посте:
Несмотря на то, что компилятор может исключить временную правую часть, ему требуется доступный, неудаляемый и неявный конструктор копирования:
auto x = std::atomic<int>{}; // fails to compile, copy constructor deleted
Если исключение не включено (например, -fno-elide-constructors
), то перемещение неперемещаемых типов приводит к дорогостоящему копированию:
auto a = std::array<int,50>{};
person
Piotr Skotnicki
schedule
01.09.2014
static_cast
, а не вместоauto
- person Piotr Skotnicki   schedule 01.09.2014static_cast
объясняется в комментарии (т.е. чтобы избежать UB). Я думаю, что автор пытается сказать использовать свое решение только для конкретных ситуаций в качестве иллюстрированного примера. Нет необходимости использовать его, когда использованиеauto
не вызывает никаких последствий. - person 101010   schedule 01.09.2014auto x = static_cast<Y>(z);
для того, что можно выразить какY x = z;
- person Piotr Skotnicki   schedule 01.09.2014Y x=e;
лучше, потому чтоauto x=static_cast<Y>(e);
активируетexplicit
конверсии, которые следует использовать с осторожностью. Я подозреваю, что аннотация, говорящая, что возвращаемое значение не должно сохраняться после вызванной строки, может быть уместна для C++. - person Yakk - Adam Nevraumont   schedule 01.09.2014