Regardless of the findings below, you don't really want to compare a string object with a null-character this way (more on that below). Really something like: if (someArduinoStringObj.isEmpty()) {
is a better idea, no matter what core you're on.
So for anyone that has landed here with this problem with code that had worked and no longer works, the answer is basically don't do that; probably shouldn't have been doing that in the first place.
If you're curious on what I found, there's material on that below.
So, I don't have a complete handle on this, but I do have something to report. When we started going through this I didn't think there would be anything going on in the Arduino board support packages that would have any effect on this, but it turns out that's not the case.
We worked out that under ESP8266 Arduino core 3.0.0 the lines of the following form fail to compile and under version 2.7.4 the compile without error. Here under 3.0.0 it was not picky about specifically loginPassword
.
if (someArduinoStringObj == '\0') someArduinoStringObj = "some literal";
This can be abstracted to the somewhat nonsensical:
someArduinoStringObj == '\0';
Since that's the part makes the difference. I really thought there was something more clever going on in the WString.h/WString.cpp code, an extra constructor or implicit conversion operator call prior to the code for the == operator. There is an explicit constructor for String
that takes a single char, but it's just that, it's explicit; it doesn't take part in evaluating the ==
expression above. And there's no automatic conversion from String
to const char *
. It turns out that isn't anything clever going on in String class when it comes to this problem. In fact you can boil this down to something similar so that the String class itself isn't actually involved at all, into:
void func(const char *) {}
void setup() {
func('\0');
}
// ...
This will likewise pass compilation under 2.7.4 and fail with a similar error message under 3.0.0. So, with String it is just trying to call the operator==(const char *)
, even though it's being given a char
type. Before getting into that, I tested a theory, which was to find out what happens if given == '\1';
or == 'X')
etc, any non-zero value, because 0 has had an interesting relationship with pointers in C++ and C. It makes more sense once you realize there's special going on in String.h. We're basically talking about func('\0')
vs func('\1')
or func('X')
Well, under 2.7.4 these values fail where '\0' does not, but under 3.0.0 both fail. So I got looking into the platform.txt file on both core versions, thinking maybe I'll spot a difference in compiler options. E.g. it would make "sense" if 2.7.4 had -fpermissive
where 3.0.0. didn't, but that's not the case. The major difference is that 3.0.0 has -std=g++17 and 2.7.4 has -std=g++11. Just for the hell of it I stole the command-line out of the verbose output compiling for the .ino.cpp file and replaced -std=g++17 with -std=g++11 and applied it to a cut-down file; didn't make any difference. So, as far I tell there's some difference in default options specified between the two compiler versions in use in the two esp8266 package versions.
Constant expressions that evaluate to zero of int
type (at least), will convert to null pointer constants. I haven't been in the habit of using character-typed ('\0'
) zero valued constant expressions for null pointers, and I haven't dug through the various editions of the C++ standard to see what each one has to say about them; color me unsurprised either way. But in any case, that's is basically what's happening here: The string object is being compared to a null pointer constant, at least when it compiles. And if that makes you go WTF, well, me too. If you follow this out, you ultimately you end up here:
unsigned char String::equals(const char *cstr) const {
if (len() == 0)
return (cstr == NULL || *cstr == 0);
if (cstr == NULL)
return buffer()[0] == 0;
return strcmp(buffer(), cstr) == 0;
}
So, roughly speaking someArduinoString == '\0'
, where it will compile, is equivalent to someArduinoString == nullptr
which is sort of the same effect as someArduinoString == ""
. So, what you're seeing is not exactly an implicit form of someArduinoString == String('\0')
.
I can't picture wanting to say someArduinoString == '\0'
over someArduinoString.isEmpty()
, but in terms of why it doesn't work on 3.0.0, I'm still unsure. I can tell you that it will if you plant -fpermissive
into platform.txt, but that doesn't really explain it. Sometime when I'm really bored I'll see what the language standard actually says and what versions of g++ did what under what options.
Even I worked that out my answer would still be: don't do that.
loginPassword
was a pointer rather thanString
, where it probably should have really been compared withnullptr
or0
depending on the era of C++.supportMode
and the others that follow this same pattern?if (X == '\0') X = some_default;
should have been writtenif (X == "")
... or maybe more efficientlyif (X.length() == 0)
..., but this is sort of nonsense for the loginPassword field. If it's already blank... it's blank.