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.
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, like char const*
.
However, the std::string
constructor from a char 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:
struct S { constexpr S(): n() {} int n; };
here, S().n
is evaluated to 0
statically (constexpr
requirement) and thus may degenerate into a pointer, while in C++03 it was of type int
. This is rather unfortunate and if you have:
std::true_type buggy(void*);
std::false_type buggy(int);
Then decltype(buggy(S().n))
returns true_type
for C++11 but false_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
A prvalue of type
bool
can be converted to a prvalue of typeint
, withfalse
becoming zero andtrue
becoming one.
§4.10 Pointer conversions [conv.ptr] p1
:
A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type
std::nullptr_t
.
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.
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:
A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type
std::nullptr_t
(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
:
An integer literal is a sequence of digits that has no period or exponent part, with optional separating single quotes that are ignored when determining its value.
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.22.27905 from Visual Studio 2019 16.2 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, which is marked as fixed in Visual Studio 2019 16.4.
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]
Change: Only literals are integer null pointer constants
Rationale: Removing surprising interactions with templates and constant expressions
Effect on original feature: Valid C++ 2003 code may fail to compile or produce different results in this International Standard, as the following example illustrates:
void f(void *); // #1 void f(...); // #2 template<int N> void g() { f(0*N); // calls #2; used to call #1 }````
Interestingly, this conversion was noted in-passing as weird in CWG97 but wasn't the point of the issue so apparently nothing was done.
[...] we have the anomalous notion that
true
andfalse
are not constant expressions.Now, you may argue that you shouldn't be allowed to convert
false
to a pointer. But [...]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With