Ответ 1
Во-первых, зачем сужать? Это происходит из § 5/10:
Многие двоичные операторы, которые ожидают операнды арифметики или типа перечисления, вызывают конверсии и дают аналогичные результаты. Цель состоит в том, чтобы дать общий тип, который также является типом результата. Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:
- [..]
- В противном случае интегральные акции (4.5) должны выполняться на обоих операндах.
где интегральная продвижение определяется в 4.5/1:
Значение целочисленного типа, отличного от
bool
,char16_t
,char32_t
илиwchar_t
, чей целочисленный ранг преобразования (4.13) меньше рангаint
, может быть преобразован в prvalue typeint
, еслиint
может представлять все значения типа источника; в противном случае исходное prvalue может быть преобразовано в prvalue типа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 );
}
Что касается решения этой проблемы - просто вызов конструктора без инициализации списка, вероятно, является самым простым решением. Кроме того, вы можете продолжать использовать инициализацию списка и просто создать шаблон своего конструктора:
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)
{ }