Является ли nullptr ложью?

Является ли nullptr постоянно ложным при использовании в качестве логического выражения или в явном или неявном виде в логическом выражении? Эта реализация определена или определена в стандарте?

Я написал некоторый код для тестирования, но не уверен, что он полностью проверяет это свойство. Я не мог найти существующий ответ SO, который говорил конкретно об этом. cppreference не упоминает об этом из того, что я вижу.

if (nullptr) {
    ;
} else {
    std::cout << "Evaluates to false implicitly\n";
}

if (!nullptr) {
    std::cout << "Evaluates to false if operated on\n";
}

if (!(bool)(nullptr)) {
    std::cout << "Evaluates to false if explicitly cast to bool\n";
}

Ожидаемый и фактический:

Evaluates to false implicitly
Evaluates to false if operated on
Evaluates to false if explicitly cast to bool

Ответы

Ответ 1

В соответствии со стандартом C++ 17 (5.13.7 литералов указателей)

1 Литерал указателя - это ключевое слово nullptr. Это тип значения станд :: nullptr_t. [Примечание: std::nullptr_t - это отдельный тип, который ни тип указателя, ни тип указателя на член; скорее, prvalue этот тип является константой нулевого указателя и может быть преобразован в значение нулевого указателя или значение указателя нулевого элемента. Смотри 7.11 и 7.12. - конечная нота]

И (7 стандартных преобразований)

4 Некоторые языковые конструкции требуют преобразования выражения к логическому значению. Выражение e, встречающееся в таком контексте, называется контекстно преобразованным в bool и хорошо сформирован, если и только если декларация bool t (e); хорошо сформирован, для некоторых изобретен временная переменная t (11.6).

И наконец (7.14 Булевых преобразований)

1 Значение арифметики, перечисление с незаданной областью, указатель или Тип указатель на член может быть преобразован в тип значения bool. нулевое значение, нулевое значение указателя или нулевое значение указателя элемента преобразуется в ложь; любое другое значение преобразуется в true. Для прямой инициализацией (11.6), значение типа std::nullptr_t может быть преобразован в значение типа bool; Полученное значение равно false.

То есть ты можешь написать например

bool b( nullptr );

но вы не можете писать (хотя некоторые компиляторы имеют ошибку по этому поводу)

bool b = nullptr;

Таким образом, nullptr может быть контекстно преобразован в объект типа bool, например, в операторах выбора, таких как оператор if.

Рассмотрим, например, унарный оператор ! как в операторе if

if ( !nullptr ) { /*...*/ }

Согласно описанию оператора (8.5.2.1 Унарные операторы)

9 Операнд оператора логического отрицания! контекстуально преобразован в bool (пункт 7); его значение истинно, если преобразованный операнд ложный и ложный в противном случае. Тип результата: bool

Поэтому nullptr в этом выражении не преобразуется в указатель. Это напрямую по контексту преобразуется в bool.

Ответ 2

Результат вашего кода гарантирован, [dcl.init]/17.8

В противном случае, если инициализация является прямой инициализацией, тип источника - std​::​nullptr_­t, а тип назначения - bool, начальное значение инициализируемого объекта - false.

Это означает, что для прямой инициализации объект bool может быть инициализирован из nullptr со значением результата false. Затем для (bool)(nullptr) nullptr преобразуется в bool со значением false.

При использовании nullptr в качестве условия if или операнда operator! он рассматривается как контекстные преобразования,

неявное преобразование выполняется, если объявление bool t(e); правильно сформировано

Это означает, что if (nullptr) и !nullptr, nullptr будут преобразованы в bool со значением false.

Ответ 3

Да, но вы должны избегать использования этого факта.

Сравнение указателей на false или 0 является распространенным типом кодирования C/C++. Я предлагаю вам избегать его использования. Если вы хотите проверить на ничтожность, используйте:

if (x == nullptr) { /* ... */}

а не

if (!x) { /* ... */}

или

if (not x) { /* ... */}

Второй вариант добавляет читателю еще одну путаницу: что такое x? Это логическое значение? Простое значение (например, целое число)? Указатель? Необязательно? Даже если x имеет осмысленное имя, оно вам мало чем поможет: if (!network_connection)... это все же может быть сложная структура, конвертируемая в целое или логическое значение, это может быть логический индикатор того, существует ли соединение, это может быть указатель, значение или необязательно. Или что-то еще.

Кроме того, помня, что nullptr оценивает как ложное, это еще один бит информации, который вам нужно хранить в глубине своего мозга, чтобы правильно декодировать код, который вы читаете. Мы можем привыкнуть к этому с давних времен или читать код других людей - но если бы мы этого не делали, не было бы очевидно, что nullptr ведет себя так. В некотором смысле это не отличается от других неясных гарантий, например, как значение в индексе 0 пустого std::string гарантированно будет \0. Только не заставляйте свой код полагаться на это, если только вам это не нужно.


PS: на самом деле в настоящее время использование нулевых указателей намного меньше. Вы можете заставить указатель никогда не быть нулевым, если им это не нужно; вы можете использовать ссылки вместо указателей; и вы можете использовать std::optional<T>, чтобы вернуть либо T, либо "no T". Возможно, вы могли бы просто не упомянуть nullptr в целом.