Здравствуйте, Homunculus, Вы писали:
H>Во-вторых, иногда нужно не-валидное значение чего-то, что по смыслу неотрицательно. И для этого я лично использую «-1». И если переменная равна ему, то значит значение невалидное. Такая штука с unsigned не проканает, если не вводить всякие magic digits конечно.
std::optional<uint>
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>>К ранеесказанному добавлю ещё один кейс. SVZ>>Индекс в массиве это полноценная сущность БД. Используется вместо указателей. SVZ>>В этом случае отрицательные значения используются для обозначения невалидных объектов (у нас это -1) и для каких-нибудь специальных констант.
R>Если вынести за скобки вопросы стиля и дизайна, то операция индексирования применима не только к массивам, но и указателям. Ведь, согласно стандарту, операция индексирования — это просто комбирация опрация сложения указателя и числа с последующим разыменованием. *(p + i), *(i + p), p[i], i[p] — все это равнозначные выражения и индекс вполне может быть отрицательным.
Неее, тут всё гораздо интереснее. Из указателя ты много информации не вытянешь, а вот из индекса в массиве очень даже.
К примеру, у нас в структурах данных некоторые связи не хранятся явно, а вычисляются на основе индекса.
// ======================================================================typedef int BRICK; //!< Single mesh celltypedef std::pair<BRICK, BRICK> BRICK2; //!< Couple of brickstypedef int FACET; //!< A facet of the cell (each cell has 6 facets)typedef int JOINT; //!< Joint between adjacent cellstypedef unsigned char FILLING; //!< ID of the Cell fillingtypedef int BRDATAEX; //!< Reference to the brick additional datatypedef int JOINTDATAEX; //!< Reference to the joint additional datatypedef int FACETREF; //!< Reference to the list of facetstypedef float PMLStretch;
typedef float PMLSigma;
typedef unsigned char JUNCTYPE; //!< Тип соединения на Joint (eJunctionFace)typedef int UPORT; //!< Индекс Micro-port'аtypedef int UPORTVAL; //!< Значение Micro-porttypedef int CORNER; //!< Угол ячейки
// ======================================================================enum
{
Facet_Brick = 6, // Число граней у ячейки - для SplitFace могут быть дополнительные subfacets
Corner_Brick = 8, // Число углов у ячейки
UPort_Facet = 2, // Число микропортов у одной грани или subfacet
UPort_Brick = 6*2, // Число микропортов у ячейки - только для Simple, PML, Conformal - больше для SplitFace Brick
IncFacet_Max = 4 // Макс. число инцидентных граней
// с одной стороны соединения
};
Вот и пример отрицательных индексов
enum { NONE = -1, DEAD = -2 };
Вот пример вычисляемых связей между сущностями
//! Получить первый порт, принадлежащий граниinline UPORT Facet2UPort1(FACET f) { return f * 2; }
//! Получить второй порт, принадлежащий граниinline UPORT Facet2UPort2(FACET f) { return f * 2 + 1; }
С указателями ты такого не сделаешь.
_____________________
С уважением,
Stanislav V. Zudin
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>Неее, тут всё гораздо интереснее. Из указателя ты много информации не вытянешь, а вот из индекса в массиве очень даже. SVZ>К примеру, у нас в структурах данных некоторые связи не хранятся явно, а вычисляются на основе индекса.
SVZ>Вот пример вычисляемых связей между сущностями SVZ>
SVZ> //! Получить первый порт, принадлежащий грани
SVZ> inline UPORT Facet2UPort1(FACET f) { return f * 2; }
SVZ> //! Получить второй порт, принадлежащий грани
SVZ> inline UPORT Facet2UPort2(FACET f) { return f * 2 + 1; }
SVZ>
SVZ>С указателями ты такого не сделаешь.
Нет-нет, я не предлагал замеменить индексы на указатели. Пусть будут индексы, и пусть они будут по-прежнему вычисляемыми. Вопрос только в том, к чему эти индексы применяются. Если индексы применяюются к массиву, они действительно не могут быть отрицательными. Но только потому что при этом заведомо возникнет выход за пределы массива, а вовсе не потому, что этого требует операция индексирования. Операция индесирования допускает использование отрицательных индексов, если при этом не возникает выхода за пределы массива — вот та мысль которую я хотел выразить.
Чтобы такой код не вызывал шевеления волос в разных местах, достаточно один раз разобраться с правилами приведения типов в выражениях C++. Правила эти почти примитивные: выражение тоже имеет тип, перед его вычислением тип обоих аргументов приводится к "большему", минимальный тип int, беззнаковый "больше" знакового.
Преобразование встроенных типов в операторах
Выражениям так же как и значениям в C++ приписывается некоторые типы. Например, если a и b — это переменные типа int, то выражения (a + b), (a — b), (a * b) и (a / b) тоже будут иметь тип int.
Важно всегда понимать, какой тип у выражения, которое вы написали в программе. Давайте проиллюстрируем это на следующем примере:
int a = 20;
int b = 50;
double d = a / b; // d = 0, оба аргумента целочисленные, а значит деление целочисленное
Как исправить этот код, чтобы получить вещественное значение в переменной d?
Для этого хотя бы один из аргументов оператора деления должен иметь типа double. Этого можно добиться при помощи уже известного нам оператора приведения типов:
double d = (double)a / b; // d = 0.4
Почему это сработало? Дело в том, что операторы для встроенных типов C++ всегда работают с одинаковыми типами аргументов. Если аргументы имеют разные типы, то происходит преобразование типов (promotion).
Правило преобразования встроенных типов в операторах
Рассмотрим выражение (a + b), где вместо '+' может стоять любой другой подходящий оператор.
Если один из аргументов имеет числовой тип с плавающей точкой, то второй аргумент приводится к этому типу (например, при сложении double и int значение типа int приводится к double).
Если оба аргумента имеют числовой тип с плавающей точкой, то выбирается наибольший из этих типов (например, при сложении double и float значение типа float приводится к double).
Если оба аргумента целочисленные, но их типы меньше int, то оба аргумента приводятся к типу int (например, при сложении двух значений типа char они оба сначала приводятся к int).
Если оба аргумента целочисленные, то аргумент с меньшим типом приводится к типу второго аргумента (например, при сложении long и int значение типа int приводится к long).
Если оба аргумента целочисленные и имеют тип одного размера, то предпочтение отдаётся беззнаковому типу (например, при сложении int и unsigned int значение типа int приводится к unsigned int).
Несколько важных следствий
Следите за тем, какие типы участвуют в выражении, от этого может зависеть его значение.
Не стоит использовать целочисленные типы меньше int в арифметических выражениях, они всё равно будут приведены к int.
Не стоит смешивать unsigned и signed типы в одном выражении, это может привести к неприятным последствиям.
Для иллюстрации последнего следствия давайте рассмотрим следующий пример:
unsigned from = 100;
unsigned to = 0;
for (int i = from; i >= to; --i) { .... }
Сколько итераций сделает этот цикл? На самом деле этот цикл — бесконечный: в условии цикла проверяется i >= to, где тип i — int, а тип to — unsigned int. Так как операторы всегда применяются к одинаковым встроенным типам, то в данном случае значение переменной i (в соответствии с правилом 5) будет преобразовано к unsigned int, а значения этого типа всегда неотрицательны, т.е. >= 0. Другими словами, когда пременная i станет равной -1, то в условии будет проверяться (unsigned)-1 >= 0, где (unsigned)-1 = UINT_MAX (UINT_MAX — максимальное значение, которое может принимать переменная типа unsigned int).
После этого любые подобные примеры являются четким критерием того, что разработчик не знаком с довольно простыми правилами преобразования типов в выражении. Просто дайте ему почитать приведенные тут правила (любой поймет и запомнит минут за 5-10).
Здравствуйте, Reset, Вы писали:
R>Чтобы такой код не вызывал шевеления волос в разных местах, достаточно один раз разобраться с правилами приведения типов в выражениях C++. Правила эти почти примитивные: выражение тоже имеет тип, перед его вычислением тип обоих аргументов приводится к "большему", минимальный тип int, беззнаковый "больше" знакового.
Ответ неверный, потому что он не учитывает человеческий фактор.
Правильный ответ: включаем /W4, подсветив все неявные касты, и убираем их из программы нафиг.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
R>>А ты уверен, что в вашей команде это не прошло бы через ревью? Такое впечатление, что зашугали тебя там так, что у тебя вполне нормальные конструкции вызывают суеверный ужасъ
TB>Да, я уверен, что цикл с кулхацкерским заголовком (а такое использование for, в котором используются побочные эффекты условия выхода, можно назвать только кулхацкерским) бы не прошёл у нас ревью.
А не может случиться так, что "кулхацкерским" этот "заголовок" является только потому, что он не вписывается в твое исходное утверждение? Я тебе из собственного опыта могу сказать, что иногда гораздо дешевле просто признать, что ошибался, чем отстаивать заведомо ошибочное утверждение. Смотри вон, сколько ты уже энергии потратил
Здравствуйте, rg45, Вы писали:
R>А не может случиться так, что "кулхацкерским" этот "заголовок" является только потому, что он не вписывается в твое исходное утверждение?
Нет, он кулхацкерский потому, что использует for не по назначению. У нормального for первое выражение — это инициализацпия, второе — это условие без побочных эффектов, а третье — это побочные эффекты. Всё остальное — это кулхацкерство.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, Nuzhny, Вы писали:
N>1. Про итерирование от N до 0 уже написали, но повторюсь.
А оценка того, насколько часто это требуется, будет?
N>2. OpenMP. Неожиданно, но Майкрософт в своём компиляторе поддерживает только очень старую версию 2.0, в ней индексы для for могут быть только знаковыми.
С какой целью хоть OpenMP, хоть что другое, использует знаковые индексы?
N>3. Адресная арифметика вся знаковая, родной тип ptrdiff_t. То есть если я захочу сделать размеры картинки (ширину и высоту) сделать, например, size_t и ходить по изображению по байтам, то мне всё равно надо переходить к ptrdiff_t, чтобы компилятор не ругался.
Вообще абсурдный аргумент. А преобразования на что? Если же главная цель — спасти себя от предупреждений компилятора, то их можно отключить.
N>4. Внезапно оказывается, что многие типы становятся знаковыми, хотя по логике они такими быть, на первый взгляд, не могут. Например, детектирую я пешеходов и авто на кадре. И их левая координата уходит в минус, если в кадре видна только часть автомобиля.
И что здесь "внезапного"? Так всегда было, есть и будет. Для координат в пределах кадра — беззнаковые величины, для координат в абстрактной системе — знаковые.
N>5. Далее знаковые становятся удобнее, когда происходит преобразование в другие системы координат. В твоей экранной системе координаты только положительные, ты рисуешь график в декартовой и числа внезапно становятся отрицательными.
На это есть преобразования типов.
N>6. Даже яркость пикселя, которая чаще всего от 0 до 255 и представлена в uchar при манипуляциях с яркостью легко вылезает за пределы типа вверх и вниз
Тоже не в кассу, здесь разрядности типа хранения и типа обработки разные.
N>Получается, что большая часть моих кейсов сводится к тому, что естественные ограничения на беззнаковость идут к херам при вычислениях с этими типами. Насколько размер одного контейнера больше второго? Нельзя просто так взять и вычесть! Надо написать if (v1.size() > v2.size())...
Когда подобных операций много, и преобразования сильно загромождают — не вопрос. Я прежде всего о тех случаях, когда ничего подобного не происходит, но типы используются знаковые, тем более — в примерах книг и статей.
Здравствуйте, Философ, Вы писали:
Ф>Особенно, когда отрицательным значением задаются размеры в структурах:
В чем разница между передачей через поле структуры и через параметр функции?
Ф>мы ведь не можем быть уверены, что переданный буфер не имеет отрицательного размера.
Что значит "буфер отрицательного размера"? Каким образом можно создать такой буфер?
Здравствуйте, T4r4sB, Вы писали:
TB>Нет, он кулхацкерский потому, что использует for не по назначению. У нормального for первое выражение — это инициализацпия, второе — это условие без побочных эффектов, а третье — это побочные эффекты. Всё остальное — это кулхацкерство.
Откуда ты черпаешь свою уверенность? Каковы твои источники? Постфиксный инкремент, походу, отменять пора, как "кулхацкерский"?
Здравствуйте, vopl, Вы писали:
V>Два наисложнейших бубена, оба без UB:
V> for(unsigned u{5+1}; u-->0; ) V> cout << u << endl;
Использование второго выражение в заголовке цикла не по назначению
V> for(unsigned u{5}; u<=5; --u) V> cout << u << endl;
Противоестественная семантика условия выхода. Да, я знаю, как работает переполнение для беззнаковых. Всё равно это лажа. К тому же она не проканает, когда надо проитерироваться от ЭН до ИКС, где ИКС это число 0 нуля включительно.
Это и есть бубны.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, Marty, Вы писали:
M>От N-1 до нуля включительно — на самом деле хотелось бы весьма часто.
На многих архитектурах вполне годится "for (uint i = N; int (i) >= 0; --i)". Если нужно совсем переносимо, то можно считать от N до 1, используя i-1, разница в объеме и скорости ничтожная.
M>Имхо это как раз стандартной библиотекой и привито
Я очень мало пользуюсь стандартной библиотекой, и только сишной. std, boost и прочие вообще никогда не использовал.
ЕМ>>Случаев, подобных упомянутому, или неочевидных ошибок за это время были единицы.
M>Так наверно преобразования возникали как раз из-за безнаковости плюсовых привычек
До "плюсовых" привычек у меня несколько лет были сишные, но я и там никогда не использовал знаковых типов для натуральных чисел.
Здравствуйте, rg45, Вы писали:
R>Постфиксный инкремент, походу, отменять пора, как "кулхацкерский"?
Ну допустим, что i++ в середине выражения ещё можно считать стандартной практикой, если i больше нигде в выражении не используется.
А какие-нибудь c=a+=b уже точно нафиг.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, T4r4sB, Вы писали:
TB>От нуля до N-1. На беззнаковых такой перебор записывается довольно неестественно.
"for (uint i = 0; i < N; ++i)". В каком месте неестественность? Или оно должно работать и при N = 0? Если так, то что обозначает 0-1 = -1, какой смысл несет это значение?
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>Индекс в массиве это полноценная сущность БД. Используется вместо указателей. SVZ>В этом случае отрицательные значения используются для обозначения невалидных объектов (у нас это -1) и для каких-нибудь специальных констант.
По-хорошему, все эти специальные константы неплохо бы именовать. А какая разница, именовать -1 или uint_max?