Одинаковы ли побитовые и логические операции для типа bool?

В C++ есть логические операции !a, a && b, a || b, применимые к значениям true/false. И у него есть побитовые операции, применимые ко всем (целочисленным?) типам, ~a, a & b, a | b, a ^ b, (a << N, a >> N). Вместе с операторами мутации на месте a &= b, a |= b, a ^= b, (a <<= b, a >>= b).

Что меня смущает, так это то, что побитовые операции могут действовать на биты bool, которые вообще не являются частью значения true/false. Даже делая ненужные битовые операции. Я предполагаю, что последний факт делает побитовый сдвиг неопределенным для bool, и это заставляет меня сомневаться в том, что все побитовые операции вообще допустимы.

Итак, эквивалентны ли они для логических типов? ~a == !a, a && b == a & b, a || b == a | b?

По какой-то причине (здесь не буду спрашивать) нет логических операторов мутации «на месте» (т. Е. a &&= b, a ||= b).

Это вдохновляет на эти вопросы: действительны ли эти другие побитовые операции для самих логических значений? всегда ли a &= b и a |= b эквивалентны a = a && b и a = a || b соответственно?**

Несколько примеров: https://godbolt.org/z/hcccG8c9o

Это отвечает на ваш вопрос: stackoverflow.com/q/32800866/104458

selbie 11.02.2023 09:41

@selbie, я экстраполирую, что в любой побитовой операции bool повышается до int (почему не char?), и это дает возможность оценить ответ на мои вопросы. Что, я думаю, положительно во всех случаях, но я плохо принимаю во внимание крайние случаи.

alfC 11.02.2023 09:48

за исключением режима короткого замыкания.

apple apple 11.02.2023 10:00

пробы и ошибки не должны быть последними, но они могут стать первым шагом к пониманию. Я не совсем понимаю ваши утверждения. Вы можете сравнить bit и bool бок о бок: godbolt.org/z/jsE6bdhqd. То, что вы сказали, продвижение к целочисленному портит ~

463035818_is_not_a_number 11.02.2023 10:06

да я вообще не понимаю зачем. Дело даже не в преобразовании обратно в bool. godbolt.org/z/nd1h1P1M4

alfC 11.02.2023 10:13

@alfC -- re "почему бы и нет char -- все целочисленные типы, которые меньше int, повышаются до int (или unsigned int, но это не имеет значения здесь), когда они используются в выражениях. Таким образом, повышение bool до char было бы просто дополнительным шаг на пути к int.

Pete Becker 11.02.2023 15:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
112
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

(Для простоты я буду предполагать C++20, т. е. представление целочисленных типов с дополнением до двух. То, что я пишу, технически может не выполняться до C++20 для всех теоретически возможных соответствующих представлений целочисленных типов.)

Что меня смущает, так это то, что побитовые операции могут воздействовать на биты логического значения, которые вовсе не являются частью значения true/false.

Встроенные побитовые операции имеют обычные арифметические преобразования , которые сначала применяются к их операндам. Для целочисленных операндов, которые подразумевают целочисленные повышения: операнды с рангом преобразования ниже int сначала преобразуются в int (или unsigned int или целочисленный тип более высокого ранга, если int не может содержать все значения исходного типа).

Поощрение всегда оставляет исходное значение без изменений. В случае bool, true отображается на 1 и false на 0. Таким образом, результат после продвижения будет иметь младший значащий бит либо установленным, либо неустановленным, в то время как все остальные биты не будут установлены. Кроме того, всегда есть как минимум 15 таких битов, потому что int должен иметь ширину не менее 16 бит.

Как следствие, ~a == !a всегда ложно. ~ установит эти дополнительные биты, а продвижение !a — нет. Однако bool(~a) == bool(!a) является true, только если a является false, потому что с установленными дополнительными битами bool(~a) всегда true.

a && b == a & b всегда верно, потому что дополнительные биты будут равны нулю при применении &.

a || b == a | b всегда верно, потому что дополнительные биты снова останутся нулевыми.

Однако, если a и b являются реальными выражениями, то есть еще одно отличие в том, что логические операторы сокращают вычисление, а побитовые — нет. Поэтому побочные эффекты могут быть разными.

Кроме того, результатом логических операций будет bool, а результатом побитовых — int. Поэтому они не являются взаимозаменяемыми, как могут, например. влияют на разрешение перегрузки, где используется их результат.

Кроме того, это относится только к встроенным операторам. Если задействованы какие-либо перегруженные операторы, ничего не гарантируется.

У меня было ощущение, что произошел какой-то нелогичный случай. ~a не эквивалентно !a очень похоже.

alfC 11.02.2023 10:24

«Если задействованы какие-либо перегруженные операторы, ничего не гарантируется». , это и есть источник моего вопроса, он заключался в том, чтобы реализовать перегрузку операторов логически (и/или побитово) согласованным образом.

alfC 11.02.2023 10:25

@alfC С точки зрения стоимости вы в основном хороши с & и |, но не с ~. Тем не менее, поведение короткого замыкания важно, как и тот факт, что составные операторы присваивания оценивают левую часть только один раз.

user17732522 11.02.2023 10:37

@alfC Я бы не стал пытаться изменить значение побитовых и логических операторов, чтобы они соответствовали одной парадигме. Это очень нелогично. Либо сохраните стандартное значение, либо не перегружайте те, которые не соответствуют парадигме, которой вы хотите следовать.

user17732522 11.02.2023 10:39

Я часто злоупотребляю перегрузкой операторов (включая (унарные) &, ~ и ,) (я могу указать вам на них). Но сколько бы я ни делал, я никогда не перегружаю &&, ||, потому что считаю это «невозможным»/«всегда неправильным», потому что логические сокращения нельзя имитировать путем перегрузки операторов.

alfC 11.02.2023 10:46

перегрузка операторов — крутая функция, пока вы не обнаружите, что не перегрузка операторов тоже довольно крутая ;)

463035818_is_not_a_number 11.02.2023 10:52

@ 463035818_is_not_a_number, не могу не согласиться. Оба классные. Если вы собираетесь это сделать, вы должны сделать это полностью: gitlab.com/correaa/boost-multi/-/blob/master/…

alfC 11.02.2023 11:19

@463035818_is_not_a_number Очевидно &(&*((~A).begin()+1))->operator()(2) == &A[2][1], godbolt.org/z/YKeK85ofo

alfC 11.02.2023 11:36

Другие вопросы по теме