Is comparing a pointer to '\0' legal?
On the trunk version of clang++ (25836be2c)
const char *a = "foo";
if(a == '\0')
gives an error: comparison between pointer and integer ('const char *' and 'int')
whereas
if(a == 0)
does not give any error as expected.
Isn't the null character equivalent to the null pointer for comparisons with pointer? Is this a compiler bug?
Another point is that this error does not show up with "-std=c++03" flag but shows up with "-std=c++11" flag. However, I don't get the error in both standards when I use g++ (v4.8.5)
This was a change from C++03 to C++14. In C++03, [conv.ptr]p1 says:
A null pointer constant is an integral constant expression rvalue of integer type that evaluates to zero.
A character literal is an integral constant expression.
In C++14, [conv.ptr]p1 says:
A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t.
A character literal is not an integer literal, nor of type std::nullptr_t.
The originally published version of C++11 didn't contain this change; however, it was introduced due to defect report DR903 and incorporated into the standard sometime after January 2013 (the date of the last comment on that DR).
Because the change is the result of a DR, compilers treat it as a bugfix to the existing standard, not part of the next one, and so Clang and GCC both made the behavior change when -std=c++11, not just when -std=c++14. However, apparently this change wasn't implemented in GCC until after version 4.8. (Specifically, it seems to have only been implemented in GCC 7 and up.)
From [conv.ptr]§1:
A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t. [...]
'\0' is not an integer literal, it's a character literal, thus the conversion does not apply.
Related
Consider the following code.
void f(double p) {}
void f(double* p) {}
int main()
{ f(1-1); return 0; }
MSVC 2017 doesn't compile that. It figures there is an ambiguous overloaded call, as 1-1 is the same as 0 and therefore can be converted into double*. Other tricks, like 0x0, 0L, or static_cast<int>(0), do not work either. Even declaring a const int Zero = 0 and calling f(Zero) produces the same error. It only works properly if Zero is not const.
It looks like the same issue applies to GCC 5 and below, but not GCC 6. I am curious if this is a part of C++ standard, a known MSVC bug, or a setting in the compiler. A cursory Google did not yield results.
MSVC considers 1-1 to be a null pointer constant. This was correct by the standard for C++03, where all integral constant expressions with value 0 were null pointer constants, but it was changed so that only zero integer literals are null pointer constants for C++11 with CWG issue 903. This is a breaking change, as you can see in your example and as is also documented in the standard, see [diff.cpp03.conv] of the C++14 standard (draft N4140).
MSVC applies this change only in conformance mode. So your code will compile with the /permissive- flag, but I think the change was implemented only in MSVC 2019, see here.
In the case of GCC, GCC 5 defaults to C++98 mode, while GCC 6 and later default to C++14 mode, which is why the change in behavior seems to depend on the GCC version.
If you call f with a null pointer constant as argument, then the call is ambiguous, because the null pointer constant can be converted to a null pointer value of any pointer type and this conversion has same rank as the conversion of int (or any integral type) to double.
The compiler works correctly, in accordance to [over.match] and [conv], more specifically [conv.fpint] and [conv.ptr].
A standard conversion sequence is [blah blah] Zero or one [...] floating-integral conversions, pointer conversions, [...].
and
A prvalue of an integer type or of an unscoped enumeration type can be converted to a prvalue of a floating-point type. The result is exact if possible [blah blah]
and
A null pointer constant is an integer literal with value zero or [...]. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type [blah blah]
Now, overload resolution is to choose the best match among all candidate functions (which, as a fun feature, need not even be accessible at the call location!). The best match is the one with exact parameters or, alternatively, the fewest possible conversions. Zero or one standard conversions may happen (... for every parameter), and zero is "better" than one.
(1-1) is an integer literal with value 0.
You can convert the zero integer literal to each of either double or double* (or nullptr_t), with exactly one conversion. So, assuming that more than one of these functions is declared (as is the case in the example), there exists more than a single candidate, and all candidates are equally good, there exists no best match. It's ambiguous, and the compiler is right about complaining.
I can't find a warning for the following in Visual Studio. I turned on /Wall but still get nothing:
const char * pointer = '\0';
gcc won't compile it for C++11, C++14, or C++17:
[x86-64 gcc 7.2 #1] error: invalid conversion from 'char' to 'const char*' [-fpermissive]
gcc will compile with the above as a warning if I pass -fpermissive:
[x86-64 gcc 7.2 #1] warning: invalid conversion from 'char' to 'const char*' [-fpermissive]
clang won't compile for C++11, C++14, or C++17:
[x86-64 clang 5.0.0 #1] error: cannot initialize a variable of type 'const char *' with an rvalue of type 'char'
I'm asking because of the below code that ended up in our codebase, apparently with no warnings:
std::ofstream file;
//...
file.write('\0', 20);
Is there a way to turn on a warning for this in Visual Studio?
Visual Studio 2015 only allows this conversion with const values of '\0'. Examples:
char c = '\0';
const char cconst = '\0';
const char * p1 = c; //error (not even warning)
const char * p2 = cconst; //ok
const char * p3 = '\0'; //ok
const char * p4 = '\1'; //error (not even warning)
The specific error is:
Error: a value of type "char" cannot be used to initialize an entity of type "const char *"
Apple LLVM 8.1.0 (clang-802.0.41) gives a warning with C++03 but an error with C++11 and later. The behavior was changed sometime between the Feb. 28, 2011 (draft N3242 and May 15, 2013 (draft N3690). I can't find the exact point.
In earlier drafts of C++, such as n1905, the OP code is defined as a valid conversion:
A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero. A null
pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable
from every other value of pointer to object or pointer to function type. Two null pointer values of the same type shall
compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not
the sequence of a pointer conversion followed by a qualification conversion (4.4).
Section 3.9.1.2 defines the signed integer types:
There are five signed integer types: “signed char”, “short int”, “int”, “long int”, and “long long int”.
This was changed in later drafts. In draft N3690 from 2013, section 4.10 says:
A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t.
A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type
and is distinguishable from every other value of object pointer or function pointer type. Such a conversion
is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The
conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the
sequence of a pointer conversion followed by a qualification conversion (4.4).
character-literal is a defined as a literal in section 2.14.1, but it does not appear in section 2.14.2. Instead it gets its own section - 2.14.3.
C++17 draft n4659 has the exact same verbiage but in different sections.
I don't see a way to give a warning for this in VS 2015. This would be another reason to run static analysis tools / other compilers to catch extra warnings.
Thanks to #EricPostpischil for the help.
The C++ standard allows the implicit conversion of zero integer constant to pointer of any type.
The following code is invalid, because the value v is not constant here:
float* foo()
{
int v = 0;
return v; // Error
}
But the following code is correct:
float* foo()
{
const int v = 0;
return v; // Ok in C++98 mode, error in C++11 mode
}
The question is: why gcc and clang (tried different versions) compile the code correctly in c++98/03 mode but return warning/error when compiled in c++11/14 mode (-std=c++11)? I tried to find the changes in C++11 working draft PDF, but got no success.
Intel compiler 16.0 and VS2015 compilers show no errors and warnings in both cases.
GCC and Clang behave differently with -std=c++11 because C++11 changed the definition of a null pointer constant, and then C++14 changed it again, see Core DR 903 which changed the rules in C++14 so that only literals are null pointer constants.
In C++03 4.10 [conv.ptr] said:
A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.
That allows all sorts of of expressions, as long as they are constant and evaluate to zero. Enumerations, false, (5 - 5) etc. etc. ... this used to cause lots of problems in C++03 code.
In C++11 it says:
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.
And in C++14 it says:
A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t.
This is a much more restrictive rule, and makes far more sense.
I have the following code, which compiles in Visual C++ 2012.
#include <string>
void func(std::string str)
{
}
void my_func()
{
func(false);
}
The boolean 'false' is implicity passed into the string constructor
string(const char* _Ptr)
And then the pointer is null (because false = 0). Why does this compile, and should it compile according to the C++11 standard?
MSVC is mistakenly treating false as a null pointer constant. However, according to N4140, §4.10 [conv.ptr]/1 (emphasis mine):
A null pointer constant is an integer literal with value zero
or a prvalue of type std::nullptr_t. A null pointer constant can be
converted to a pointer type; the result is the null pointer value of
that type and is distinguishable from every other value of object
pointer or function pointer type.
The wording changed a bit from C++11, and you can find that discussion here. The verdict there was that it was an error in C++11 as well.
For visibility, TartanLlama provided the definition of "integer literal" below, according to [lex.icon]/1:
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.
I found a bug in my code where I compared the pointer with '\0'.
Wondering why the compiler didn't warn me about this bug I tried the following.
#include <cassert>
struct Foo
{
char bar[5];
};
int main()
{
Foo f;
Foo* p = &f;
p->bar[0] = '\0';
assert(p->bar == '\0'); // #1. I forgot [] Now, comparing pointer with NULL and fails.
assert(p->bar == 'A'); // #2. error: ISO C++ forbids comparison between pointer and integer
assert(p->bar[0] == '\0'); // #3. What I intended, PASSES
return 0;
}
What is special about '\0' which makes #1 legal and #2 illegal?
Please add a reference or quotation to your answer.
What makes it legal and well defined is the fact that '\0' is a null pointer constant so it can be converted to any pointer type to make a null pointer value.
ISO/IEC 14882:2011 4.10 [conv.ptr] / 1:
A null pointer constant is an integral constant expression prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion.
'\0' meets the requirements of "integral constant expression prvalue of integer type that evaluates to zero" because char is an integer type and \0 has the value zero.
Other integers can only be explicitly converted to a pointer type via a reinterpret_cast and the result is only meaningful if the integer was the result of converting a valid pointer to an integer type of sufficient size.
'\0' is simply a different way of writing 0. I would guess that this is legal comparing pointers to 0 makes sense, no matter how you wrote the 0, while there is almost never any valid meaning to comparing a pointer to any other non-pointer type.
This is a design error of C++. The rule says that any integer constant expression with value zero can be considered as the null pointer constant.
This idiotic highly questionable decision allows to use as null pointer '\0' (as you found) but also things like (1==2) or even !!!!!!!!!!!1 (an example similar to one that is present on "The C++ programming language", no idea if Stroustrup thinks this is indeed a "cool" feature).
This ambiguity IMO even creates a loophole in the syntax definition when mixed with ternary operator semantic and implicit conversions rules: I remember finding a case in which out of three compilers one was not compiling and the other two were compiling with different semantic ... and after wasting a day on reading the standard and asking experts on c.c.l.c++.m I was not able to decide which of the three compilers was right.