присвоение true / false std :: string: что происходит? - PullRequest
15 голосов
/ 12 марта 2012

Я тестировал компилятор c ++ 11 в моем исходном коде, и он обнаружил ошибку в одной из моих функций, которую я ожидал, что мой компилятор не c ++ 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 = c ++ 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 предлагает

Ответы [ 4 ]

6 голосов
/ 12 марта 2012

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

std::string принимает конструктор из указателя, ложное значение уменьшается до 0, что становится нулевым указателем.

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

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

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

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

std::string str = nullptr;

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

3 голосов
/ 12 марта 2012

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

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

§4.5 Integral promotions [conv.prom] p4

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

§4.10 Pointer conversions [conv.ptr] p1

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

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

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

2 голосов
/ 12 марта 2012

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

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

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

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


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

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

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

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

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

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

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

0 голосов
/ 24 марта 2017

Ответ Xeo правильный до C ++ 11.

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

A константа нулевого указателя является целочисленным литералом (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.10.25109.0 из Visual Studio 2017 успешно компилирует его, поэтому он не совместим с C ++ 14. Я не смог найти открытую проблему сообщества разработчиков Microsoft Visual Studio, поэтому я подал одну .


Этот вопрос был поднят в Комитет по стандартам 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 в указатель. Но [...]

...