Присвоение 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
в указатель. Но [...]