Suivre la norme C++ :
§ 8.5 Initialisateurs [dcl.init]
-
L'initialisation qui se produit dans la forme
T x = a;
ainsi que dans le passage des arguments, le retour de la fonction, le lancement d'une exception (15.1), la gestion d'une exception (15.3) et l'initialisation des membres de l'agrégat (8.5.1). copier-initialisation .
Je peux penser à l'exemple donné dans le livre :
auto x = features(w)[5];
comme celle qui représente toute forme de copier-initialisation avec le type auto / modèle ( type déduit en général), tout comme :
template <typename A>
void foo(A x) {}
foo(features(w)[5]);
ainsi que :
auto bar()
{
return features(w)[5];
}
ainsi que :
auto lambda = [] (auto x) {};
lambda(features(w)[5]);
Donc le fait est que nous ne pouvons pas toujours _"déplacer le type T de static_cast<T>
à la partie gauche de l'affectation"_ .
Au contraire, dans tous les exemples ci-dessus, nous devons spécifier explicitement le type désiré plutôt que de laisser le compilateur en déduire un par lui-même, si ce dernier peut conduire à comportement indéfini :
Respectivement à mes exemples, ce serait :
/*1*/ foo(static_cast<bool>(features(w)[5]));
/*2*/ return static_cast<bool>(features(w)[5]);
/*3*/ lambda(static_cast<bool>(features(w)[5]));
Ainsi, l'utilisation de static_cast<T>
est une manière élégante de forcer un type désiré, qui peut être exprimé de manière alternative par un appel explicite au contructeur :
foo(bool{features(w)[5]});
Pour résumer, je ne pense pas que le livre dise :
Chaque fois que vous voulez forcer le type d'une variable, utilisez auto x = static_cast<T>(y);
au lieu de T x{y};
.
Pour moi, ça ressemble plus à un mot d'avertissement :
L'inférence de type avec auto
est cool, mais peut aboutir à un comportement indéfini s'il est utilisé de manière imprudente.
Et comme solution pour les scénarios impliquant une déduction de type il est proposé ce qui suit :
Si le mécanisme régulier de déduction de type du compilateur n'est pas ce que vous voulez, utilisez static_cast<T>(y)
.
UPDATE
Et pour répondre à votre nouvelle question, Quelle est l'initialisation à privilégier parmi les suivantes :
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]};
Scénario 1
D'abord, imaginez le std::vector<bool>::reference
est pas implicitement convertible en bool
:
struct BoolReference
{
explicit operator bool() { /*...*/ }
};
Maintenant, le bool priority = features(w)[5];
sera ne pas compiler car il ne s'agit pas d'un contexte booléen explicite. Les autres fonctionneront sans problème (tant que l'option operator bool()
est accessible).
Scénario 2
Deuxièmement, supposons que le std::vector<bool>::reference
est mis en œuvre dans un vieille méthode et bien que le opérateur de conversion n'est pas explicit
il renvoie int
à la place :
struct BoolReference
{
operator int() { /*...*/ }
};
Le changement de signature s'éteint le site auto priority = bool{features(w)[5]};
l'initialisation, comme l'utilisation de {}
empêche le rétrécissement (qui convertit un int
à bool
est).
Scénario 3
Troisièmement, et si nous ne parlions pas de bool
du tout, mais sur certains défini par l'utilisateur qui, à notre grande surprise, déclare explicit
constructeur :
struct MyBool
{
explicit MyBool(bool b) {}
};
De manière surprenante, une fois de plus, le MyBool priority = features(w)[5];
l'initialisation sera ne pas compiler car la syntaxe d'initialisation de la copie nécessite un constructeur non explicite. Les autres fonctionnent cependant.
Attitude personnelle
Si je devais choisir une initialisation parmi les quatre candidates citées, je choisirais :
auto priority = bool{features(w)[5]};
parce qu'il introduit un contexte booléen explicite (ce qui est bien dans le cas où nous voulons assigner cette valeur à une variable booléenne) et empêche le rétrécissement (dans le cas d'autres types, difficilement convertibles en bool), de sorte que lorsqu'une erreur/alerte est déclenchée, nous pouvons diagnostiquer ce qui est à l'origine de l'erreur. features(w)[5]
est vraiment .
MISE À JOUR 2
J'ai récemment regardé le discours d'Herb Sutter de CppCon 2014 intitulé Retour aux sources ! L'essentiel du style C++ moderne où il présente quelques points sur les raisons pour lesquelles il faut préférer le initialisateur de type explicite de auto x = T{y};
(bien que ce ne soit pas la même chose qu'avec le formulaire auto x = static_cast<T>(y)
(tous les arguments ne s'appliquent donc pas) sur T x{y};
qui sont :
-
auto
Les variables doivent toujours être initialisées. Autrement dit, vous ne pouvez pas écrire auto a;
tout comme vous pouvez écrire des erreurs int a;
-
Le site C++ moderne préfère le type sur le côté droit, tout comme dans :
a) Les littéraux :
auto f = 3.14f;
// ^ float
b) Les littéraux définis par l'utilisateur :
auto s = "foo"s;
// ^ std::string
c) Les déclarations de fonctions :
auto func(double) -> int;
d) lambdas nommés :
auto func = [=] (double) {};
e) Alias :
using dict = set<string>;
f) Alias de modèles :
template <class T>
using myvec = vector<T, myalloc>;
en tant que tel en ajoutant un autre :
auto x = T{y};
est cohérent avec le style où nous avons le nom sur le côté gauche, et le type avec initialisateur sur le côté droit, ce qui peut être brièvement décrit comme :
<category> name = <type> <initializer>;
-
Avec la copie-élision et les constructeurs non explicites de copie/déplacement, il a coût nul par rapport à T x{y}
la syntaxe.
-
Elle est plus explicite lorsqu'il existe des différences subtiles entre les types :
unique_ptr<Base> p = make_unique<Derived>(); // subtle difference
auto p = unique_ptr<Base>{make_unique<Derived>()}; // explicit and clear
-
{}
garantit l'absence de conversions implicites et de rétrécissement.
Mais il mentionne également certains inconvénients de la auto x = T{}
en général, qui a déjà été décrit dans ce post :
-
Même si le compilateur peut élider le temporaire du côté droit, il faut un copieur-constructeur accessible, non supprimé et non explicite :
auto x = std::atomic<int>{}; // fails to compile, copy constructor deleted
-
Si l'élision n'est pas activée (par ex. -fno-elide-constructors
), alors le déplacement des types non mobiles entraîne une copie coûteuse :
auto a = std::array<int,50>{};
4 votes
J'ai lu ses livres (pas celui-ci), et je doute qu'il n'ait pas donné d'explications.
6 votes
@Niall : D'après ce que je comprends, la question n'est pas de savoir pourquoi c'est nécessaire, mais pourquoi mettre le type en
static_cast
plutôt qu'à la place deauto
0 votes
@PiotrS. C'est exactement ce que je voulais dire.
0 votes
@B malheureusement non. Il a ajouté une note complémentaire pour un autre cas, où vous devez intentionnellement passer de double à int.
1 votes
@PiotrS. Pourquoi mettre le type dans
static_cast
est expliqué dans le commentaire (c'est-à-dire pour éviter l'UB). Je pense que ce que l'auteur essaie de dire, c'est de n'utiliser sa solution que dans des situations spécifiques comme l'exemple illustré. Il n'est pas nécessaire de l'utiliser lorsque l'utilisation deauto
n'entraîne aucune conséquence.11 votes
@40two : nah... la question est de savoir pourquoi Scott a inventé un idiome qui ressemble :
auto x = static_cast<Y>(z);
pour ce qui peut être exprimé commeY x = z;
4 votes
Et..,
Y x=e;
est meilleure parce queauto x=static_cast<Y>(e);
activeexplicit
qu'il convient d'utiliser avec prudence. Je soupçonne qu'une annotation indiquant que la valeur de retour ne doit pas être conservée au-delà de la ligne invoquée pourrait être utile pour le C++.1 votes
En ce qui concerne votre test pour le rétrécissement, ce code ne devrait pas être compilé. Le compilateur que vous avez vérifié n'implémente pas correctement l'initialisation des accolades. Il est probable que cela sera corrigé. rextester.com/SFTL92198