У меня есть следующий код:
template <typename T>
struct wrapper {
T t;
operator T() { return t; }
T get() { return t; }
};
int main() {
int a[10];
int* x = a;
wrapper<long unsigned int> y{2};
std::cout << (x + y); // warning
}
Когда я компилирую его на gcc (проверено на 7.3.0 и 8.2.0) с -Wsign-conversion, я получаю «предупреждение: преобразование в 'long int' из 'long unsigned int' может изменить знак результата». Если y имеет тип long unsigned int, предупреждения нет. Более того, когда я явно вызываю y.get(), также нет предупреждения:
std::cout << (x + y.get()); // this is ok
Почему это так? Существуют ли какие-то особые правила для арифметики указателей, которые нельзя использовать при использовании пользовательского преобразования?
Для x + y выполняется разрешение перегрузки и выбирается встроенный кандидат T* operator+(T*, std::ptrdiff_t);. Однако преобразование из long unsigned int в std::ptrdiff_t фактически не происходит, и кажется, что GCC игнорирует это при создании предупреждения.
@DanielLangr правильно, я отредактировал вопрос, так что теперь a является типом массива.





(thanks @liliscent for correcting what I said here earlier)
Во-первых, давайте сделаем один MCVE для всего упомянутого вами утверждения:
#include <iostream>
template <typename T>
struct wrapper {
T t;
operator T() const { return t; }
T get() const { return t; }
};
int main() {
int a[10];
int* x { a } ;
wrapper<long int> y1{2};
wrapper<unsigned int> y2{2};
wrapper<long unsigned int> y3{2};
std::cout << (x + y1) << '\n';
std::cout << (x + y2) << '\n';
std::cout << (x + y3) << '\n'; // this triggers a warning
std::cout << (x + y3.get()) << '\n';
}
и используя GCC 8.2.0, мы получить:
<source>: In function 'int main()':
<source>:20:23: warning: conversion to 'long int' from 'long unsigned int' may change the sign of the result [-Wsign-conversion]
std::cout << (x + y3) << '\n';
^~
Compiler returned: 0
по ссылке вы увидите, как:
Так что это должен быть какой-то угловой случай w.r.t. соответствие стандартам.
Теперь я уверен, что есть сложное техническое объяснение того, почему вы получаете ошибку только на 3-м из этих потоковых операторов. Но я утверждаю, что это не имеет значения с точки зрения практического использования. Если вы будете добавлять в указатели только правильные целые числа - как вы это делаете с оператором .get() - вы не получите этого предупреждения.
Видите ли, вы пытаетесь добавить к указателю определяемый пользователем тип, что обычно не имеет особого смысла. Верно, что ваша структура может быть преобразована в целое число, но, полагаясь на это преобразование, неявно открывает вам такие вещи, как выбор преобразования другого операнда или другие пути преобразования, которые вы не рассматривали. Более того, это случаи, когда вы можете «поразить» некоторую эзотерическую оговорку в стандарте, касающуюся того, когда неявные преобразования допустимы, что может или не может быть реализовано компилятором с идеальной корректностью (см. Комментарий @ cppcleaner).
Так что просто используйте x + y3.get() в своем коде, и вам не придется беспокоиться об этих тайных уголках.
Я привел аналогичный аргумент в этот ответ относительно использования индекса 0 пустой строки (да, это вещь).
Боюсь, это не отвечает на поставленный вопрос. Я бы полностью понял, что это вторая часть ответа, где первая часть объясняет причину. Чтобы ответ был таким: «вот почему; но, пожалуйста, сделайте что-нибудь разумное, например, это». Но без 1-й части это не ответ. Понимание того, почему что-то происходит, может быть более ценным, чем «решение, которое работает, потому что оно работает».
@Angew: В некоторых случаях важно понять, почему что-то происходит. В этом случае я утверждаю, что для нет важно понять, почему это происходит (если вы не изучаете поведение GCC, а не пишете C++). Но я постараюсь немного отредактировать, чтобы это подчеркнуть.
@Angew ... но все равно добавил первую часть к ответу.
-Wsign-conversion, чтобы включить предупреждение, или -Weverything@einpoklum Предупреждение существует в clang 6.0, но не в 7.0. скорее всего ошибка.
Обратите внимание, что арифметика указателей IIRC определена только для элементов массива.