Присвоение true/false std::string: что происходит?

Я тестировал компилятор С++ 11 в своем исходном коде, и он поймал ошибку в одной из моих функций, которую я ожидал бы от моего компилятора non С++ 11. Я возвращал false из функции, которая имеет тип возврата std::string... Здесь код, демонстрирующий проблему

#include <iostream>

int main ( )
{
    std::string str = false;

    std::cerr << "'" << str << "'" << std::endl;

    return 0;
}


$ g++ test.cpp -W -Wall -Wextra
$ ./a.out

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct NULL not valid
Aborted

Я очень удивлен, что этот код компилируется без проблем. Я подозреваю, что из описания исключения заключается в том, что компилятор преобразует false в 0 и затем в NULL и использует это как char *, чтобы попытаться построить строку.

Однако, когда я переключаю false на true, вот что я получаю:

$ g++ test.cpp -W -Wall -Wextra
test.cpp: In function ‘int main()’:
test.cpp:5: error: conversion from ‘bool’ to non-scalar type ‘std::string’ requested

Это более разумный результат, на мой взгляд.

Может кто-то прояснить, почему это, казалось бы, непоследовательное поведение происходит? То есть std::string a = false компилируется, но генерирует исключение, а std::string a = true не компилируется.

EDIT:

Для справки, здесь ошибка, сгенерированная с g++ 4.7 с -std = С++ 11 для ложного случая:

test.cpp: In function ‘int main()’:
test.cpp:5:23: warning: converting ‘false’ to pointer type for argument 1 of ‘std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-Wconversion-null]

Он принимает NULL, хотя, поскольку CashCow предлагает

Ответы

Ответ 1

Это скорее ужасное неявное преобразование и отсутствие безопасности типов.

std::string принимает конструктор из указателя false деградирует до 0, который становится нулевым указателем.

и вы не можете передать нулевой указатель на конструктор std::string.

Кстати, пока вы используете = это конструктор, а не задание, которое вы здесь выполняете.

Ваш "строгий" компилятор g++ С++ 11, однако, хорошо поймал ошибку для вас во время компиляции.

И он не будет работать с true, потому что он никогда не может представлять указатель NULL. С++ 11 имеет значение nullptr. Если вы попытались:

std::string str = nullptr;

ваш компилятор С++ 11, вероятно, скомпилирует его, а затем вы получите ошибку времени выполнения.

Ответ 2

В точности, как вы говорите, false может быть преобразовано в действительную константу нулевого указателя (к сожалению).

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

§4.5 Integral promotions [conv.prom] p4

Значение типа bool может быть преобразовано в prvalue типа int, с false, становящимся равным нулю и true.

§4.10 Pointer conversions [conv.ptr] p1:

Константа нулевого указателя является интегральным постоянным выражением (5.19) prvalue целочисленного типа , который оценивается в 0 или значением класса std::nullptr_t.

Так как false является литералом, он также является интегральным постоянным выражением, и после продвижения по службе действительно оценивается до нуля.

Обратите внимание, что это не изменилось в С++ 11. На самом деле приведенные выше цитаты из стандарта С++ 11. Что вы получаете с GCC 4.7 - это просто предупреждение. Это необязательная диагностика, которую ваш компилятор решил намекнуть, так как это всегда неправильно и ошибка.

Ответ 3

Это тонкая проблема, которую я не могу полностью понять.

Основное правило состоит в том, что все, что имеет значение 0, может считаться действительным нулевым указателем. Поэтому false может использоваться в контекстах, требующих указателя, например char const*.

Однако конструктор std::string из char const* явно требует указатель не нуль (и здесь вам посчастливилось получить исключение).

С другой стороны, true не является 0 и поэтому не может рассматриваться как указатель. Таким образом, вы получаете правильную диагностику.


Этот вопрос усугубляется введением constexpr в С++ 11, который был поднят Richard Smith:

struct S { constexpr S(): n() {} int n; };

здесь S().n оценивается как 0 статически (constexpr требование) и, следовательно, может вырождаться в указатель, тогда как в С++ 03 он имеет тип int. Это довольно неудачно, и если у вас есть:

std::true_type buggy(void*);
std::false_type buggy(int);

Затем decltype(buggy(S().n)) возвращает true_type для С++ 11, но false_type с С++ 03, довольно неудачное изменение в семантике.

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

У Clang есть предупреждения, доступные для этих странных преобразований: -Wbool-conversions.

Ответ 4

Xeo ответ правильный, включая C++ 11.

В C++ 14 соответствующий текст в §4.10 Pointer conversions [conv.ptr] p1 был изменен:

Константа нулевого указателя - это целочисленный литерал (2.14.2) со значением ноль или значением типа std::nullptr_t

(Смелый акцент мной)

Таким образом, false остается целочисленным константным выражением через §4.5 Integral promotions [conv.prom] p4, это не целочисленный литерал: §2.14.2 Integer literals [lex.icon] p1:

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


gcc 4.5 предупреждает об этом, а gcc 6.1 считает это ошибкой независимо от флага -std=c++1?.

Visual C++ 19.22.27905 из Visual Studio 2019 16.2 с радостью компилирует его, так что не C++ 14-совместим. Мне не удалось найти открытую проблему сообщества разработчиков Microsoft Visual Studio, поэтому я подал одну, которая помечена как исправленная в Visual Studio 2019 16.4.


Эта проблема была поднята в Комитет по стандартам ISO C++ как CWG1448 в 2012 году и исправлена как часть резолюции CWG903 (поднятой в 2009 году, но решенной в 2013 году).

Изменения в стандартной формулировке видны в CWG903 Константы целочисленного нулевого указателя, зависящие от значения, которые добавили следующий блок текста в список отличий от C++ 03 до текущего стандарта: §C2.2 Clause 4: standard conversions [diff.cpp03.conv]

Изменение: Только литералы являются константами целочисленных нулевых указателей

Обоснование: Удаление неожиданных взаимодействий с шаблонами и константными выражениями

Эффект на исходную функцию: Действительный код C++ 2003 может не скомпилироваться или привести к другим результатам в этом международном стандарте, как показано в следующем примере:

void f(void *); // #1 
void f(...); // #2
template<int N> void g() {
 f(0*N); // calls #2; used to call #1
}''''

Интересно, что это преобразование было отмечено как проходное как странное в CWG97, но не в этом суть проблемы, так что, очевидно, ничего не было сделано.

[...] у нас есть аномальное представление о том, что true и false не являются константными выражениями.

Теперь вы можете утверждать, что вам нельзя разрешать конвертировать false в указатель. Но [...]