Здравствуйте, Marty, Вы писали:
M>Есть свой класс, он реализован так, что ведёт себя как интегральный тип.
Так заведение оператора приведения к интегральному типу — это как индикатор того, что декларируемая цель не достигнута.
M>Вопрос — сделать этот оператор explicit или нет? Если сделать explicit, то он уже не будет вести себя как интегральный — компилятор не сможет его неявно преобразовать в интегральный тип.
С опереторами неявного приведения можно огрести таких сюрпризов, котрые нарочно и не придумаешь. Я считаю, что такой оператор — это какой-то очень редкий исключительный случай. И если у тебя нет уверенности. что этот тот самый случай (а у тебя её нет, насколько я могу судить), то лучше этого не делать.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Muxa, Вы писали:
M>Тогда компилятор будет выбирать операторы в зависимости от порядка вычислений в выражениях, если ему нужно будет сложить два инта то он сложит два инта. И насколько я знаю, поправьте меня знатоки стандарта, этот порядок не всегда определен. Например, x = v + u + z — хз в каком порядке будет вычислено в итоге.
Например, в выражении foo(a) + bar(b) * baz(c) однозначно сначала выполнится умножение, потом сложение. А вот в каком порядке будут вызваны функции foo, bar, baz — это как решит компилятор.
--
Справедливость выше закона. А человечность выше справедливости.
M>>А это как-то влияет не результат вычислений в рантайме?
M>У меня "интегральный" тип с произвольным размером. Если будет преобразование в интегральный тип, то будет усечение, если мой — усечения не будет. Да, в этом плане мой тип ведёт себя не совсем как интегральный, тут я наврал
Тогда компилятор будет выбирать операторы в зависимости от порядка вычислений в выражениях, если ему нужно будет сложить два инта то он сложит два инта. И насколько я знаю, поправьте меня знатоки стандарта, этот порядок не всегда определен. Например, x = v + u + z — хз в каком порядке будет вычислено в итоге.
Здравствуйте, rg45, Вы писали: M>>Тогда компилятор будет выбирать операторы в зависимости от порядка вычислений в выражениях, если ему нужно будет сложить два инта то он сложит два инта. И насколько я знаю, поправьте меня знатоки стандарта, этот порядок не всегда определен. Например, x = v + u + z — хз в каком порядке будет вычислено в итоге. R>Тут важно не путать порядок выполнения операций и порядок вычисления значений операндов. Порядок выполнения операций определяется их приоритетами и ассоциативностью и строго определён: https://en.cppreference.com/w/cpp/language/operator_precedence. Совсем другое дело — порядок вычисления операндов (подвыражений) — он может быть любым (unspecified): https://en.cppreference.com/w/cpp/language/eval_order.html. R>Например, в выражении foo(a) + bar(b) * baz(c) однозначно сначала выполнится умножение, потом сложение. А вот в каком порядке будут вызваны функции foo, bar, baz — это как решит компилятор.
Так, но не совсем так.
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
Для выражения x = v + u + z можно подобрать такие v + u + z, что в зависимости от порядка выполнения операций промежуточный результат может как оставаться в пределах или же выходить за пределы. В стандарте есть разъяснения по этому поводу.
разъяснения
5 [Note 4 : The implementation can regroup operators according to the usual mathematical rules only where the
operators really are associative or commutative.41 For example, in the following fragment
int a, b;
/* ... */
a = a + 32760 + b + 5;
the expression statement behaves exactly the same as
a = (((a + 32760) + b) + 5);
due to the associativity and precedence of these operators. Thus, the result of the sum (a + 32760) is next added to
b, and that result is then added to 5 which results in the value assigned to a. On a machine in which overflows produce
an exception and in which the range of values representable by an int is [-32768, +32767], the implementation cannot
rewrite this expression as
a = ((a + b) + 32765);
since if the values for a and b were, respectively, −32754 and −15, the sum a + b would produce an exception while
the original expression would not; nor can the expression be rewritten as either
a = ((a + 32765) + b);
or
a = (a + (b + 32765));
since the values for a and b might have been, respectively, 4 and −8 or −17 and 12. However on a machine in which
overflows do not produce an exception and in which the results of overflows are reversible, the above expression
statement can be rewritten by the implementation in any of the above ways because the same result will occur. —end
note]
Вывод: порядок зависит от архитектуры.
Отдельно, по теме, стоить заметить, что
41) Overloaded operators are never assumed to be associative or commutative.
А раз у Marty есть свои операторы, то они запрещают компилятору менять порядок. Казалось бы. На самом деле компилятор может менять порядок как ему заблагорассудится до тех пор, пока результат остаётся таким, как если бы порядок не менялся.
Есть свой класс, он реализован так, что ведёт себя как интегральный тип. Есть конструктор из интегрального типа. Надо также сделать оператор преобразования в интегральный тип operator int/operator T.
Вопрос — сделать этот оператор explicit или нет? Если сделать explicit, то он уже не будет вести себя как интегральный — компилятор не сможет его неявно преобразовать в интегральный тип.
С другой стороны, если в выражении участвует мой тип и интегральные типы, то мне надо, чтобы интегральные преобразовывались в мой тип, а не наоборот, так как мой тип шире. Что будет выбирать компилятор, если ему доступен конструктор моего типа из интегрального и не-explicit оператор преобразования в интегральный тип?
Здравствуйте, Muxa, Вы писали:
M>>Что будет выбирать компилятор, если ему доступен конструктор моего типа из интегрального и не-explicit оператор преобразования в интегральный тип?
M>А это как-то влияет не результат вычислений в рантайме?
У меня "интегральный" тип с произвольным размером. Если будет преобразование в интегральный тип, то будет усечение, если мой — усечения не будет. Да, в этом плане мой тип ведёт себя не совсем как интегральный, тут я наврал
Здравствуйте, Muxa, Вы писали:
M>Тогда компилятор будет выбирать операторы в зависимости от порядка вычислений в выражениях, если ему нужно будет сложить два инта то он сложит два инта. И насколько я знаю, поправьте меня знатоки стандарта, этот порядок не всегда определен. Например, x = v + u + z — хз в каком порядке будет вычислено в итоге.
Я не большой знаток стандарта, но имхо компилятор будет вычислять точно так, как ему сказано. Порядок вычисления не определён при вычислении аргументов функции, но это другое
Если бы компилятор мог произвольно менять порядок вычисления в вырважениях, то не работали (или произвольно работали бы, на разных компиляторах по разному, и/или в зависимости от уровня оптимизации) бы подобные конструкции:
if ((a*10/10)!=a)
{
//...
}
Но ты меня натолкнул на мысль — надо переопределить ещё все MyInt operator X(integral_type right) для интегральных типов, и сделать двухместные friend операторы MyInt operator X(integral_type left, MyInt right)[/tt] для моего MyInt.
Тогда всё будет работать как надо, и можно сделать operator integral_type() не explicit, и всё будет работать нормасик
Для умножения и деления может быть да — порядок определен, а для сложения и вычитания я хз.
Я, кстати, тоже свой тип писал для fp32 вычислений, чтобы эмулировать на цпу вычисления как они происходят на видеокарте с точностью до последнего бита мантиссы.
Здравствуйте, Muxa, Вы писали:
M>Для умножения и деления может быть да — порядок определен, а для сложения и вычитания я хз.
Почему ты считаешь, что для умножения и деления правила другие, нежели чем для сложения и вычитания? У операторов с равным приоритетом порядок вычисления строго слева направо, иначе, если в выражении есть пользовательские типы, то разные компиляторы и/или один компилятор с разными уровнями оптимизации выводили бы даже разный тип результата выражений.
M>Я, кстати, тоже свой тип писал для fp32 вычислений, чтобы эмулировать на цпу вычисления как они происходят на видеокарте с точностью до последнего бита мантиссы.
Хм, а там не IEEE-шные fp32? Если IEEE-шные fp32, то чем они отличаются от IEEE-шных на CPU? А если не IEEE-шные, то тогда на разных видеочипах это может быть по разному, надо писать реализацию под каждый тип видеочипа
M>>Для умножения и деления может быть да — порядок определен, а для сложения и вычитания я хз.
M>Почему ты считаешь, что для умножения и деления правила другие, нежели чем для сложения и вычитания? У операторов с равным приоритетом порядок вычисления строго слева направо, иначе, если в выражении есть пользовательские типы, то разные компиляторы и/или один компилятор с разными уровнями оптимизации выводили бы даже разный тип результата выражений.
Поэтому я сказал что не знаю как в стандарте этот момент описан.
M>>Я, кстати, тоже свой тип писал для fp32 вычислений, чтобы эмулировать на цпу вычисления как они происходят на видеокарте с точностью до последнего бита мантиссы.
M>Хм, а там не IEEE-шные fp32? Если IEEE-шные fp32, то чем они отличаются от IEEE-шных на CPU? А если не IEEE-шные, то тогда на разных видеочипах это может быть по разному, надо писать реализацию под каждый тип видеочипа
IEEE-шные, но на цпу это все может вычисляется множеством вариантов: интринсики/сопроцессор/расширенные 80-битные регистры и результат в общем случае не совпадает.
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Есть свой класс, он реализован так, что ведёт себя как интегральный тип. Есть конструктор из интегрального типа. Надо также сделать оператор преобразования в интегральный тип operator int/operator T.
M>Вопрос — сделать этот оператор explicit или нет? Если сделать explicit, то он уже не будет вести себя как интегральный — компилятор не сможет его неявно преобразовать в интегральный тип.
M>С другой стороны, если в выражении участвует мой тип и интегральные типы, то мне надо, чтобы интегральные преобразовывались в мой тип, а не наоборот, так как мой тип шире. Что будет выбирать компилятор, если ему доступен конструктор моего типа из интегрального и не-explicit оператор преобразования в интегральный тип?
так вроде очевидно. Если он будет не explicit, то выражение 1 + my_integral_type{2} + 3 приведёт к ambitious при выборе функции
Здравствуйте, Marty, Вы писали:
M>С другой стороны, если в выражении участвует мой тип и интегральные типы, то мне надо, чтобы интегральные преобразовывались в мой тип, а не наоборот, так как мой тип шире. Что будет выбирать компилятор, если ему доступен конструктор моего типа из интегрального и не-explicit оператор преобразования в интегральный тип?
Я как-то пробовал на C++ написать тип, который ведет себя, как int. Не то, что бы я мастер C++, но по-моему, это невозможно.
Здравствуйте, rg45, Вы писали:
M>>Есть свой класс, он реализован так, что ведёт себя как интегральный тип.
R>Так заведение оператора приведения к интегральному типу — это как индикатор того, что декларируемая цель не достигнута.
Ну почему? У меня есть функция, принимающая int, например. При вызове этой функции вычисляется какое-то выражение. Я хочу сменить используемый в выражении тип с интегрального на свой MyInt. При отсутствии неявного operator int мне надо будет переписать выражение, добавив явный каст к интегральному типу. Это может иногда быть просто невозможно, в каких-нибудь чужих шаблонах.
R>С опереторами неявного приведения можно огрести таких сюрпризов, котрые нарочно и не придумаешь. Я считаю, что такой оператор — это какой-то очень редкий исключительный случай. И если у тебя нет уверенности. что этот тот самый случай (а у тебя её нет, насколько я могу судить), то лучше этого не делать.
А можно примеры сюрпризов? Вот с приведением к bool через void* сюрпризы бывали, и для этого добавили explicit operator bool, но, не смотря на ключевое слово explicit, это работает без явного преобразования к bool в контексте условных операторов.
Здравствуйте, sergii.p, Вы писали:
SP>так вроде очевидно. Если он будет не explicit, то выражение 1 + my_integral_type{2} + 3 приведёт к ambitious при выборе функции
А если у меня есть все версии operator+, которые могут принимать MyInt и integral_type в любой комбинации? Тоже будет амбигус? Но, по идее, тут ведь не нужен ни ctor MyInt(integral_type), ни MyInt::operator integral_type().
Здравствуйте, Pzz, Вы писали:
Pzz>Я как-то пробовал на C++ написать тип, который ведет себя, как int. Не то, что бы я мастер C++, но по-моему, это невозможно.
Чой-то?
Можешь попинать и/или потестить мой вариант, и найти, где он ведёт себя не как int (кроме, возможно, некоторых случаев со сдвигами и битовыми операторами). Но возможные несовместимости связаны только с тем, что у меня тип произвольной размерности, если бы я делал длинный int-тип фиксированного размера, то он был бы вообще идентичен встроенному.
Здравствуйте, Marty, Вы писали:
M>Ну почему? У меня есть функция, принимающая int, например. При вызове этой функции вычисляется какое-то выражение. Я хочу сменить используемый в выражении тип с интегрального на свой MyInt. При отсутствии неявного operator int мне надо будет переписать выражение, добавив явный каст к интегральному типу. Это может иногда быть просто невозможно, в каких-нибудь чужих шаблонах.
Ну смотри — для инта функция есть, а для твоего типа у тебя такой функции нет. Уже получается, что твой тип не подобен инту. Ты скармливаешь этой функции инт ВМЕСТО своего типа. Не важно, явно или неявно.
M>А можно примеры сюрпризов?
Ну вот из недавнего. Разбирали один супер-пупер говно-класс типа универсальной обертки разного вида строк. И чего там в этом классе только не было — и функции и операторы, в т.ч. и неявного приведения. А вот чего не было в этом классе — так это операторов three-way сравнения. А именно эти операторы использовались в ассоциативных контейнерах стандартной библиотеки. В результате, при использовании объектов этого чудо-класса в качестве ключа мапы использовался не мудреный оператор сравнения, а оператор преобразования к const char*.
Мало? Еще пример: написали функцию, возвращающую "универсальную" ссылку, рассчитывая на то, что на выходе получится lvalue ссылка на оригинальный объект. Но в игру вмешался оператор неявного преобразования и вместо этого получилась rvalue ссылка на верменный объект. И оказалась эта ссылка битой, сразу же после выхода из функции.
Да и главная сложность не в том или другом частном примере, а в многообразии труднопредсказуемых "сюрпризов", которые способны подарить неявные преобразования.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Marty, Вы писали:
M>А если у меня есть все версии operator+, которые могут принимать MyInt и integral_type в любой комбинации? Тоже будет амбигус? Но, по идее, тут ведь не нужен ни ctor MyInt(integral_type), ни MyInt::operator integral_type().
Горький опыт говорит, что, чем больше наопределяешь операторов, тем с большей вероятностью получишь амбигус и тем труднее его будет разрулить.
Здравствуйте, Pzz, Вы писали:
M>>Можешь попинать и/или потестить
Pzz>Не хотю
Дело хозяйское
Pzz>P.S. Тебе RSA приспичило переписать?
Та не, нафик мне тот RSA
Pzz>Зачем тебе big int?
Шобы был. Надоело отслеживать всякие переполнения. Ну и у меня есть уже Decimal, он заточен под десятичные числа с плавающей точкой произвольной длины, но из-за десятичности он не оптимален для хранения целых. Захотел ему в пару более эффективный целый тип.
Здравствуйте, B0FEE664, Вы писали:
BFE>Для выражения x = v + u + z можно подобрать такие v + u + z, что в зависимости от порядка выполнения операций промежуточный результат может как оставаться в пределах или же выходить за пределы. В стандарте есть разъяснения по этому поводу. BFE>
BFE>5 [Note 4 : The implementation can regroup operators according to the usual mathematical rules only where the
BFE>operators really are associative or commutative.41 For example, in the following fragment-*
BFE>Вывод: порядок зависит от архитектуры.
Я все же склонен рассматривать это как особый случай и не торопился бы обобщать. В общем же случае порядок выполнения операций зависит и от приоритетов, и от ассоциативностей. Например, в выражении a — b + c компилер сначала выполнит вычитание и только потом сложение, но ни как не наоборот, хотя приоритеты у сложения и вычитания одинаковы. И это только потому, что обе эти операция являются левоассоциативными. То же самое с умножением и делением.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Горький опыт говорит, что, чем больше наопределяешь операторов, тем с большей вероятностью получишь амбигус и тем труднее его будет разрулить.
Я таки думаю, что если граамотно всё сделать, то будет работать
Здравствуйте, Marty, Вы писали:
R>>Горький опыт говорит, что, чем больше наопределяешь операторов, тем с большей вероятностью получишь амбигус и тем труднее его будет разрулить.
M>Я таки думаю, что если граамотно всё сделать, то будет работать
Ну это да, с прямыми руками можно сделать многое. Но вот с этим оператором преобразования ты зря затеял. Если есть другие варианты, то лучше обойтись без этого оператора.
--
Справедливость выше закона. А человечность выше справедливости.
Чой-то неправильно? Результат 42+5000000000=0x12A05F22A — 9 hex цифр, int, очевидно, 4х-байтный, усекается до 0x2A05F22A = 705032746
BFE>Кстати, тоже самое для BFE>
BFE>public: // to integral convertion
BFE>
BFE>возвращаемый результат moduleToIntegralConvertionHelper(t) не проверяется.
И не должен, так и задумано. Разве компилятор тебе что-то говорит, когда у тебя в рантайме при работе с интегральными типами происходят переполнения/усечения? Так и тут я сделал.
Для того, чтобы сконвертировать в интегральный тип с проверкой, у меня есть метод
template < typename T, std::enable_if_t< std::is_integral_v<T>, int> = 0 >
T checkedConvert(bool *pValid=0) const
{
if (pValid)
*pValid = true;
if constexpr (std::is_signed_v<T>)
{
std::int64_t t = 0;
moduleToIntegralConvertionHelper(t);
if (pValid)
{
if (t > std::int64_t(std::numeric_limits<T>::max()))
*pValid = false;
}
return T(t);
}
else
{
std::uint64_t t = 0;
moduleToIntegralConvertionHelper(t);
if (pValid)
{
if (t > std::uint64_t(std::numeric_limits<T>::max()))
*pValid = false;
else if (t < std::uint64_t(std::numeric_limits<T>::min()))
*pValid = false;
}
return T(t);
}
}
Тут я, правда, слегка косякнул, и забыл проверить результат moduleToIntegralConvertionHelper, надо пофиксить
Здравствуйте, Marty, Вы писали:
BFE>>Кстати, тоже самое для BFE>>
BFE>>public: // to integral convertion
BFE>>
BFE>>возвращаемый результат moduleToIntegralConvertionHelper(t) не проверяется.
M>И не должен, так и задумано.
Непонятно зачем.
M>Разве компилятор тебе что-то говорит, когда у тебя в рантайме при работе с интегральными типами происходят переполнения/усечения?
Вообще-то, если тип знаковый, то это UB
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
А если тип беззнаковый, то всё считается по модулю.
M>Так и тут я сделал.
С какой целью? В каком сценарии переполненный int не приводит к ошибке?
Здравствуйте, rg45, Вы писали:
R>И чего там в этом классе только не было — и функции и операторы, в т.ч. и неявного приведения. А вот чего не было в этом классе — так это операторов three-way сравнения.