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;
}
Я получаю предупреждение:
!!warning: narrowing conversion of ‘(((int)((vec3<char>*)this)->vec3<char>::x) - ((int)other.vec3<char>::x))’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]
Но если я сделаю это с (...)
поставленный {...}
внутри operator-
Функция предупреждения исчезает. Зачем?
Во-первых, почему сужение? Это исходит из §5 / 10:
Многие бинарные операторы, которые ожидают операнды арифметического или перечислимого типа, вызывают преобразования и выдают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата. Эта модель называется обычные арифметические преобразования, которые определены следующим образом:
— [..]
— В противном случае интегральные преобразования (4.5) должны выполняться для обоих операндов.
где интегральное продвижение определено в 4.5 / 1:
Значение типа integer, отличного от
bool
,char16_t
,char32_t
, или жеwchar_t
чей ранг целочисленного преобразования (4.13) меньше, чем рангint
может быть преобразовано в тип значенияint
еслиint
может представлять все значения типа источника; в противном случае исходное значение может быть преобразовано в значение типаunsigned int
,
В нашем случае мы имеем decltype(char + char)
является int
так как char
конверсионный рейтинг меньше чем int
так что оба повышены до int
до звонка operator+
, Теперь у нас есть int
с, что мы передаем конструктор, который принимает char
s. По определению (§8.5.4 / 7, в частности 7.4):
сужение конверсии это неявное преобразование
(7.4) — от целочисленного типа или перечислимого типа с незаданной областью до целочисленного типа, который не может представлять все значения исходного типа, за исключением случаев, когда источником является константное выражение, значение которого после интегральных повышений будет соответствовать целевому типу.
что явно запрещено при инициализации списка, в частности, согласно §8.5.4 / 3 (выделение мое, «см. ниже» на самом деле относится к тому, что я только что скопировал выше):
Инициализация списка объекта или ссылки типа
T
определяется следующим образом— [..]
— В противном случае, если
T
является типом класса, конструкторы рассматриваются. Применимые конструкторы перечисляются, и лучший выбирается через разрешение перегрузки (13.3, 13.3.1.7). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. Ниже), программа является некорректной. […]
Вот почему ваш vec3<T>{int, int, int}
выдает предупреждение: программа плохо сформирована из-за целочисленного продвижения, требующего сужающего преобразования для всех выражений. Теперь утверждение о «плохо сформированном» конкретно возникает только в контексте инициализации списка. Вот почему, если вы инициализируете свой вектор без {}s
Вы не видите это предупреждение:
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 );
}
Что касается решения этой проблемы — просто вызов конструктора без инициализации списка, возможно, является самым простым решением. В качестве альтернативы, вы можете продолжать использовать list-initialization и просто шаблонировать ваш конструктор:
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)
{ }
Пара вещей здесь происходит. Во-первых, {...}
синтаксис запрещает неявные сужающие преобразования. Так что легко исправить, поменяв фигурные скобки на круглые скобки:
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>( x - other.x, y - other.y, z - other.z );
}
Второе, что происходит: «ага, чёрный минус чарс — чарс, в чем проблема ?!» И ответ здесь заключается в том, что C / C ++ хотят использовать натуральный размер для арифметических операций. Вот почему вы видите (int)
приведите ваше сообщение об ошибке. Вот хорошее объяснение почему он это делает (на тот случай, если ответ StackOverflow исчезнет, он цитирует 6.3.1.1 стандарта C11).
Итак, другой способ исправить ваш код:
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)
};
}
Кстати, пункт 7 в Effective Modern C ++ убедил меня, что бывают случаи, когда ()
лучше инициализировать с, и бывают случаи, когда {}
лучше. Иногда нужно просто пожать плечами и использовать другой.