Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

assigning true/false to std::string: what's going on?

Tags:

c++

string

std

gcc

g++

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

like image 492
vmpstr Avatar asked Mar 12 '12 16:03

vmpstr


4 Answers

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.

like image 156
CashCow Avatar answered Oct 07 '22 02:10

CashCow


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.

like image 44
Matthieu M. Avatar answered Oct 07 '22 04:10

Matthieu M.


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 type int, with false becoming zero and true 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.

like image 3
Xeo Avatar answered Oct 07 '22 04:10

Xeo


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 and false are not constant expressions.

Now, you may argue that you shouldn't be allowed to convert false to a pointer. But [...]

like image 3
TBBle Avatar answered Oct 07 '22 02:10

TBBle