Подтвердить что ты не робот

Предупреждение: сужение преобразования С++ 11

g++ 4.9.0 -O2 -std = С++ 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]

Но если я делаю это с (...) insted из {...} внутри функции operator-, предупреждение исчезает. Почему?

4b9b3361

Ответ 1

Во-первых, зачем сужать? Это происходит из § 5/10:

Многие двоичные операторы, которые ожидают операнды арифметики или типа перечисления, вызывают конверсии и дают аналогичные результаты. Цель состоит в том, чтобы дать общий тип, который также является типом результата. Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:

- [..]

- В противном случае интегральные акции (4.5) должны выполняться на обоих операндах.

где интегральная продвижение определяется в 4.5/1:

Значение целочисленного типа, отличного от bool, char16_t, char32_t или wchar_t, чей целочисленный ранг преобразования (4.13) меньше ранга int, может быть преобразован в prvalue type int, если 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)
{ } 

Ответ 2

Здесь происходит несколько вещей. Во-первых, синтаксис {...} запрещает неявное сужение конверсий. Таким образом, легкое исправление заключается в замене фигурных скобок на скобки:

vec3<T> operator-(const vec3<T> &other) { 
  return vec3<T>( x - other.x, y - other.y, z - other.z ); 
}

Второе: "eh? char минус char - это char, в чем проблема?!" И ответ здесь заключается в том, что 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 С++ убедил меня в том, что есть моменты, когда () лучше инициализировать, и бывают случаи, когда {} лучше. Иногда вам нужно просто пожать плечами и использовать другую.