Изменяющееся поведение для возможной потери точности

Насколько я могу судить, это ошибка clang.

Инициализация списка копирования имеет довольно неинтуитивное поведение: он считает явные конструкторы жизнеспособными до тех пор, пока разрешение перегрузки не будет полностью завершено, но может отклонить результат перегрузки, если выбран явный конструктор. Формулировка в проекте post-N4567 [over.match.list] p1

В инициализации списка копий, если выбран конструктор explicit, инициализация плохо сформирована. [ Примечание: Это отличается от других ситуаций (13.3.1.3, 13.3.1.4), где рассматриваются только конструкторы преобразования для инициализации копирования. Это ограничение применяется только в том случае, если эта инициализация является частью окончательного результата разрешения перегрузки. - end note ]

blockquote>

clang HEAD принимает следующую программу:

#include 
using namespace std;

struct String1 {
    explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
    String2(const char*) { cout << "String2\n"; }
};

void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }

int main()
{
    //f1( {"asdf"} );
    f2( {"asdf"} );
    f( {"asdf"} );
}

Которая, за исключением комментирования звоните в f1, прямо из N2532 Bjarne Stroustrup - Унифицированная инициализация , глава 4. Благодаря Johannes Schaub для показа мне эту статью на std-обсуждение .

В той же главе содержится следующее объяснение:

Реальное преимущество explicit в том, что оно делает ошибку f1("asdf") ошибкой. Проблема в том, что разрешение перегрузки «предпочитает» конструкторы не explicit, так что f("asdf") вызывает f(String2). Я считаю, что разрешение f("asdf") меньше идеала, потому что автор String2, вероятно, не имел целью разрешить двусмысленности в пользу String2 (по крайней мере, не в каждом случае, когда явные и неявные конструкторы происходят так) и писатель String1, конечно, этого не делал. Правило благоприятствует «неаккуратным программистам», которые не используют explicit.

blockquote>

Насколько мне известно, N2640 - Списки инициализаторов - Альтернативный механизм и обоснование - последняя статья, которая включает в себя обоснование такого разрешения перегрузки; его преемник N2672 был проголосован в черновик C ++ 11.

Из его главы «Значение явного»:

Первый подход для того чтобы сделать пример плохо сформированным, требуется, чтобы все конструкторы (явные и неявные) рассматривались для неявных преобразований, но если выбран явный конструктор, эта программа плохо сформирована. Это правило может ввести свои собственные сюрпризы; например:

struct Matrix {
    explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);

struct Pixel {
    Pixel(int row, int col);
};
Pixel transpose(Pixel);

Pixel p = transpose({x, y}); // Error.

Второй подход заключается в том, чтобы игнорировать явные конструкторы при поиске жизнеспособности неявного преобразования, но включать их при фактическом выборе конструктора преобразования: если явный конструктор заканчивается выбранный, программа плохо сформирована. Этот альтернативный подход позволяет последнему примеру (Pixel-vs-Matrix) работать как ожидалось (выбран transpose(Pixel)), в то время как исходный пример («X x4 = { 10 };») плохо сформирован.

blockquote>

Хотя в этом документе предлагается использовать второй подход, его формулировка представляется ошибочной - в моей интерпретации формулировки она не приводит к поведению, изложенному в обоснованной части статьи. Формулировка пересмотрена в N2672, чтобы использовать первый подход, но я не мог найти никакого обсуждения о том, почему это было изменено.


Конечно, немного больше формулировок, связанных с инициализацией переменной, как в OP, но учитывая разницу в поведении между clang и gcc, то же самое для первой программы-образца в моем ответе, я думаю, что это касается основных моментов.

30
задан polygenelubricants 2 May 2010 в 10:28
поделиться

1 ответ

Это потому, что b + = 1.0; эквивалентно b = (int) ((b) + (1.0) ); . Сужающее примитивное преобразование (JLS 5.1.3) скрыто в операции составного присваивания.

Операторы составного присваивания JLS 15.26.2 (третье издание JLS):

Выражение составного присваивания в форме E1 op = E2 эквивалентно E1 = (T) ((E1) op (E2)) , где T - это тип E1 , за исключением того, что E1 оценивается только один раз.

Например, следующий код верен:

 short x = 3; 
x + = 4.6; 
 

и приводит к x , имеющему значение 7 , потому что оно эквивалентно:

 short x = 3; 
x = (short) (x + 4.6); 
 

Это также объясняет, почему следующий код компилируется:

byte b = 1;
int x = 5;
b += x; // compiles fine!

Но этого не происходит:

byte b = 1;
int x = 5;
b = b + x; // DOESN'T COMPILE!

В этом случае вам необходимо явное приведение:

byte b = 1;
int x = 5;
b = (byte) (b + x); // now it compiles fine!

Стоит отметить, что неявное приведение в составных присваиваниях является предметом Головоломки 9: Tweedledum из замечательной книги Java Puzzlers .Вот отрывок из книги (слегка отредактированный для краткости):

Многие программисты думают, что x + = i; - это просто сокращение для x = x + i; . Это не совсем так: если тип результата шире, чем тип переменной, оператор составного присваивания выполняет тихое сужающее примитивное преобразование.

Чтобы избежать неприятных сюрпризов, не используйте составные операторы присваивания для переменных типа byte , short или char . При использовании составных операторов присваивания для переменных типа int убедитесь, что выражение в правой части не относится к типу long , float или ] двойной . При использовании составных операторов присваивания для переменных типа float убедитесь, что выражение в правой части не относится к типу double . Этих правил достаточно, чтобы компилятор не генерировал опасные сужающие приведения.

Для разработчиков языков, вероятно, будет ошибкой составные операторы присваивания генерировать невидимые приведения; составные присваивания, в которых переменная имеет более узкий тип, чем результат вычисления, вероятно, должны быть недопустимыми.

Последний абзац стоит отметить: C # намного строже в этом отношении (см. Спецификация языка C # 7.13.2 Составное присваивание ).

34
ответ дан 28 November 2019 в 00:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: