g ++ 4.9.0 -O2 -std = c ++ 11
template<class T>
struct vec3 {
T x, y, z;
vec3() = default;
vec3(const vec3<T> &other) = default;
vec3(T xx, T yy, T zz) { x = xx; y = yy; z = zz; }
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>{ x - other.x, y - other.y, z - other.z };
}
};
int main() {
vec3<char> pos{ 0, 0, 0 };
vec3<char> newPos{ 0, 0, 0 };
auto p = pos - newPos;
return 0;
}
Je reçois l'avertissement:
!!warning: narrowing conversion of ‘(((int)((vec3<char>*)this)->vec3<char>::x) - ((int)other.vec3<char>::x))’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]
Mais si je le fais avec (...)
au lieu de {...}
à l'intérieur de operator-
fonction l'avertissement disparaît. Pourquoi?
D'abord, pourquoi rétrécir? Cela vient du §5/10:
De nombreux opérateurs binaires qui attendent des opérandes de type arithmétique ou énumération provoquent des conversions et produisent des types de résultats de manière similaire. Le but est de produire un type commun, qui est également le type du résultat. Ce modèle est appelé conversions arithmétiques habituelles , qui sont définies comme suit:
- [..]
- Sinon, les promotions intégrales (4.5) doivent être effectuées sur les deux opérandes.
où la promotion intégrale est définie en 4.5/1:
Une valeur d'un type entier autre que
bool
, _char16_t
_, _char32_t
_ ou _wchar_t
_ dont le rang de conversion entier (4.13) est inférieur au rang deint
peut être converti en une valeur de typeint
siint
peut représenter toutes les valeurs du type source; sinon, la valeur source peut être convertie en une valeur de type _unsigned int
_.
Dans notre cas alors, nous avons decltype(char + char)
est int
parce que le classement de conversion de char
est inférieur à int
, donc les deux sont promus en int
avant l'appel à _operator+
_. Maintenant, nous avons int
s que nous transmettons à un constructeur qui prend char
s. Par définition (§8.5.4/7, spécifiquement 7.4):
Une conversion rétrécissante est une conversion implicite
(7.4) - d'un type entier ou d'un type d'énumération non étendue à un type entier qui ne peut pas représenter toutes les valeurs du type d'origine, sauf lorsque la source est une expression constante dont la valeur après les promotions intégrales s'adaptera au type cible.
ce qui est explicitement interdit dans l'initialisation de liste spécifiquement conformément au §8.5.4/3 (c'est moi qui souligne, le "voir ci-dessous" fait référence à ce que je viens de copier ci-dessus):
L'initialisation de liste d'un objet ou d'une référence de type
T
est définie comme suit- [..]
- Sinon, si
T
est un type de classe, les constructeurs sont pris en compte. Les constructeurs applicables sont énumérés et le meilleur est choisi par résolution de surcharge (13.3, 13.3.1.7). Si une conversion restreinte (voir ci-dessous) est nécessaire pour convertir l'un des arguments, le programme est mal formé. [...]
C'est pourquoi votre _vec3<T>{int, int, int}
_ vous donne un avertissement: le programme est mal formé en raison de la promotion d'entiers nécessitant une conversion plus étroite sur toutes les expressions. Maintenant, la déclaration sur les "mal formés" ne se pose spécifiquement que dans le contexte de l'initialisation de la liste. C'est pourquoi si vous initialisez votre vecteur sans _{}s
_, vous ne voyez pas cet avertissement:
_vec3<T> operator-(const vec3<T> &other) {
// totally OK: implicit conversion from int --> char is allowed here
return vec3<T>( x - other.x, y - other.y, z - other.z );
}
_
En ce qui concerne la résolution de ce problème - le simple fait d'appeler le constructeur sans initialisation de liste est probablement la solution la plus simple. Alternativement, vous pouvez continuer à utiliser l'initialisation de liste et simplement modèle votre constructeur:
_template <typename A, typename B, typename C>
vec3(A xx, B yy, C zz)
: x(xx) // note these all have to be ()s and not {}s for the same reason
, y(yy)
, z(yy)
{ }
_
Il se passe deux ou trois choses ici. Premièrement les {...}
la syntaxe interdit les conversions de rétrécissement implicites. La solution la plus simple consiste donc à remplacer les accolades par des parenthèses:
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>( x - other.x, y - other.y, z - other.z );
}
La deuxième chose qui se passe est, "hein? Char moins un char est un char, quel est le problème?!" Et la réponse ici est que C/C++ veut utiliser la taille naturelle pour les opérations arithmétiques. C'est pourquoi vous voyez le (int)
intégrez votre message d'erreur. Voici une bonne explication pourquoi il fait cela (juste au cas où la réponse StackOverflow disparaîtrait, il cite 6.3.1.1 de la norme C11).
Donc, l'autre façon de corriger votre code est:
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>{
static_cast<char>(x - other.x),
static_cast<char>(y - other.y),
static_cast<char>(z - other.z)
};
}
Soit dit en passant, l'élément 7 dans Effective Modern C++ m'a convaincu qu'il y a des moments où ()
est préférable d'initialiser avec, et il y a des moments où {}
est mieux. Parfois, il suffit de hausser les épaules et d'utiliser l'autre.