C++
Comments 22
0
но явные преобразования тоже не работают
Так работают же!?:
if (bool(withValidation))  // ok
+2
Имеются в виду контекстные преобразования.

Есть один момент, который не все знают. Если объявить оператор преобразования как explicit:

    struct A {
        explicit operator bool() { return false; }
    };


то компилятор не сможет делать неявные преобразования:

    auto foo = [](bool) { return; };

    A a;
    foo(a); // не компилируется


А контекстные — сможет:

    while (a);
    do { if (a) assert(false); } while (a);
    assert(!a && (a || true));
    for(a ? 5 : 6; a;);
    // кроме того, explicit operator bool допускает использование объектов
    // класса в выражениях static_assert и noexcept

0
работа с байтами медленнее, чем с двойными словами (если речь про x86).
0
Нет, тут дело не в этом.
Проблема производительности в том, что если у нас есть переменная val целого типа, то компилятор в выражении
bool f = val;

может сгенерировать (внимание, ниже псевдокод для демонстрации поведения) нечто вроде такого:
bool f = val == 0 ? false : true;

Это предупреждение было призвано обозначить возможность потенциальной генерации такого кода в этом месте, на случай, если программист об этом не подумал.
Чтобы не быть голословным, приведу практический пример с дизассемблером:
godbolt.org/g/FQG8Jk
Как видим, в безобидном присваивании появилось условие. Данный warning об этом.

При этом, если посмотреть выданную выше ссылку, то можно увидеть в ответе на report такие слова
My understanding of the history of C4800 was that it was introduced a very long time ago (~1990?) to address concerns for developers migrating from C.
Это коссвенно указывает на тоже самое, о чем я говорил выше. Т.к. в С не было типа bool, то программисты на нем, при переходе на С++ могли быть неприятно удивлены, что одинаковый внешне код в С++ приводит к генерации «лишних» инструкций, чего в С не наблюдалось. Поэтому на каких-то этапах это предупреждение действительно было полезно.
+1
Очевидно, автор использует два параметра типа bool как флаги (toggles)

Обычно, бывает так, что там где два подобных параметра, там со временем их становится 10. И как ты эти параметры не называй лучше от этого не станет.
Здесь нужно сосредоточить свои силы не на том как назвать эти параметры, а в принципе пересмотреть подобный интерфейс с отдачей наружу функции с 2-10 флажками (раз уж мы говорим о C++), тем более если это какая-та обработка. Например, декомпозировать, выделить отдельные сущности (возможно полиморфные), определить входные/выходные параметры, выстроить процесс обработки обходом четко определенных объектов (все зависит от конкретной задачи). Поддержать хоть какую-то гибкость и расширяемость.


Т.е. проблема здесь не во флажках, а в коде типа


...
if (withNewEngine)
    do_something_new();
else
    do_something_old();
...
+1
Комментаторы в оригинальной статье тоже это заметили. После самой статьи я привожу перевод нескольких таких комментариев с ответами автора.
0
На самом деле часто бывает так:


// Many lines of code

if(flag) {
// A few lines
}
+12
Проблема поставлена хорошо, да и решение предложено неплохое. Но ИМХО использование перечислений — шаг вперёд, а завязка их на булевы значения (и тем более каст к ним) — шаг назад. При определении перечислений нужно забыть, что переменные, из-за которых они возникают, были из множества {false, true}. Т.е. вместо
enum class WithValidation : bool { False, True };
enum class WithNewEngine  : bool { False, True };

я бы написал как-то так:
enum class ValidationMode : bool { NoValidation, Validation };
enum class EngineVersion : bool { Old, New };

Тогда и мыслей о касте к булеву значению не возникает: мы будем проверять строгое соответствие переменной одному из её возможных значений.

Кстати, по опыту, часто функции (bool,…, bool) не обрабатывают весь спектр 2 в степени n индивидуально, она просто болеет «добавлением ещё флажка». Т.е., например, если у нас есть функция
void PaintControl( bool is_visible, bool is_enable );

То при первом аргументе false второй вообще не важен: какая разница, доступный контрол или нет, если мы его даже не будем отрисовывать по факту. Из-за этого напрашивается сделать что-то такое:
enum class ControlVisibility { Hidden, Disabled, Enabled };
void PaintControl( ControlVisibility visibility );
-1
Ну, Вы уже зашли туда, где нужен объект с тремя состояниями в качестве параметра, а статья все-таки про флажки.

Автор пишет, что использовать в качестве флагов перечисления или tagged_bool — это вопрос личных предпочтений. Почитайте комментарий ARNAUD к оригинальной статье — я его тоже перевел.
0

К сожалению, часто такие функции из сторонних библиотек. И наряду с подходом добавления комментариев


process(/*withValidation=*/ false, /*withNewEngine=*/ true);

Сущес��вует подход именованных локальных переменных:


constexpr auto withValidation = false;
constexpr auto withNewEngine = false;
process(withValidation, withNewEngine);
0
Кажется мне, что читабельности это не добавляет. Булевы переменные должны читаться утвердительно. То есть
constexpr auto withValidation = true;
constexpr auto noValidation = false;
constexpr auto withNewEngine = true;
constexpr auto withOldEngine = false;
process(noValidation, withOldEngine);


0
Булевы переменные должны читаться утвердительно.

На мой взгляд, это зависит от гайдлайнов, принятых при кодировании. Мне, например, выражение с двойным отрицанием сильно режет глаз:


constexpr auto noValidation = false;

Для меня было бы привычней:


    {
        constexpr auto validation = false;
        process(validation);
    }

    {
        constexpr auto validation = true;
        process(validation);
    }

Кажется мне, что читабельности это не добавляет.

Часто это избавляет от перехода h-файлу (для того что бы увидеть прототип функции).

0
Главное, чтобы вызов функции читался однозначно. То есть или присвоение и вызов подряд (как у вас), или имя булевой переменной несёт в себе и значение (как у меня).
Но не определили переменную withValidation = false, а потом используем её кучу раз ниже по тексту.
В итоге, если отлаживаешь нижний вызов, то уже наивно думаешь, что process(withValidation) это всё-таки с валидацией.
В совсем крайнем случае можно флаг назвать нейтрально (Validation, Engine), как в вашем случае, чтобы не было ненужных ассоциаций
0

Что бы вызов функции читался однозначно она не должна иметь параметров и быть членом класса.


something.process();
+4

Какие-то проблемы на ровном месте


enum { WithValidation=1, NewEngine=2 };
void process( int flags );
...
process( NewEngine | WithValidation );
UFO landed and left these words here
+1
Вы создаёте лишние проблемы и сущности на ровном месте. Если не нравятся флаги можно писать так:
struct Process {
  typedef Process This;
  Process() {
    validation=false;
    new_engine=false;
  }
  int run() {
    //...
    return 0;
  }
  This& WithValidation(bool value=true) { validation=value; return *this; }
  This& UseNewEngine(bool value=true) { new_engine=value; return *this; }
protected:
  bool validation, new_engine;
};
...
Process process;
process
  .UseNewEngine()
  .WithValidation(false)
  .run();

Но с флагими работет. И работает хорошо. А как гласит народная мудрость: работает не трож.
0
--Я вообще не люблю флаги. Они противоречат принципу единственной ответственности.

этy проблему в какой-то мере решили в С# добавляя к любому типу состояние NULL.

bool? value[] = {true, false, null};
0
В таких случаях у нас обычно флаги конвертируются в set из enum-ов.
И сразу все становится понятно.
Only those users with full accounts are able to leave comments., please.