I was testing a c++11 compiler on my source code and it caught an error in one of my functions that I would have expected my non c++11 compiler to catch as well. I was returning false from a function that has a return type of std::string... Here's the code that demonstrates the problem
#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
I'm very surprised that this code compiles with no problems. I suspect from the exception description is that the compiler is converting a false to 0 and then to NULL and uses that as a char * to try and construct the string..
However, when I switch false to true, here's what I get:
$ 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
That's a more reasonable result, in my opinion.
Can someone please clarify why this seemingly inconsistent behaviour happens? That is, std::string a = false
compiles, but throws an exception, and std::string a = true
doesn't compile.
EDIT:
For reference, here's an error generated with g++ 4.7 with -std=c++11 for the false case:
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]
It does accept NULL though as CashCow suggests
It's rather a horrible implicit conversion and lack of type-safety.
std::string
takes a constructor from a pointer false degrades to 0 which becomes a null pointer.and you cannot pass a null pointer to the constructor of std::string.
Incidentally whilst you use = it is a constructor not an assignment you are performing here.
Your "strict" g++ C++11 compiler however nicely caught the error for you at compile time.
And it won't work with true because that is never able to represent a NULL pointer. C++11 has nullptr. If you tried:
std::string str = nullptr;
your C++11 compiler would probably compile it and then you'd get a runtime error.
Xeo's answer is correct up-to and including C++11.
In C++ 14, the relevant text in
§4.10 Pointer conversions [conv.ptr] p1
was changed:(Bold emphasis by me)
So
false
remains an integral constant expression via§4.5 Integral promotions [conv.prom] p4
, it is not a integer literal:§2.14.2 Integer literals [lex.icon] p1
:gcc 4.5 warns about this, and gcc 6.1 holds this as an error, irrespective of the
-std=c++1?
flag.Visual C++ 19.10.25109.0 from Visual Studio 2017 happily compiles it, so that's not C++14-compliant. I couldn't find an open Microsoft Visual Studio Developer Community issue, so I lodged one.
This issue was raised to the ISO C++ Standards Committee as CWG1448 in 2012 and fixed as part of the resolution of CWG903 (raised in 2009 but resolved in 2013).
The change to the standard wording is visible at CWG903 Value-dependent integral null pointer constants which added the following block of text to the list of differences from C++03 to the current standard:
§C2.2 Clause 4: standard conversions [diff.cpp03.conv]
Interestingly, this conversion was noted in-passing as weird in CWG97 but wasn't the point of the issue so apparently nothing was done.
It is a subtle issue that I may not fully understand.
The basic rule is that anything that has a value of
0
may be considered a valid null pointer. Therefore,false
can be used in contexts requiring a pointer, likechar const*
.However, the
std::string
constructor from achar const*
explicitly requires a non-null pointer (and here you are fortunate to get an exception).On the other hand,
true
is non-0
, and so cannot be treated as a pointer. Thus you get a proper diagnostic.This issue is compounded by the introduction of
constexpr
in C++11, which was raised by Richard Smith:here,
S().n
is evaluated to0
statically (constexpr
requirement) and thus may degenerate into a pointer, while in C++03 it was of typeint
. This is rather unfortunate and if you have:Then
decltype(buggy(S().n))
returnstrue_type
for C++11 butfalse_type
with C++03, a rather unfortunate change in semantics.Richard's proposal is to change this from an implicit conversion to a standard conversion to help in this case, however I don't think that it would help much in yours.
Clang has warnings available for those weird conversions:
-Wbool-conversions
.It's exactly as you say,
false
can be converted to a valid null pointer constant (sadly so).true
, however, is not a null pointer constant and can't be converted to one and as such can't be converted to a pointer and fails to compile.§4.5 Integral promotions [conv.prom] p4
§4.10 Pointer conversions [conv.ptr] p1
:Since the
false
is a literal, it's also an integral constant expression, and after promotion indeed evaluates to zero.Note that this has not changed in C++11. In fact, the above quotes are from the C++11 standard. What you get with GCC 4.7 is just a warning. It's an optional diagnostic that your compiler decided to hint at, since it's always wrong and a bug.