C++ experts - implicit conversion of String to C-string?

Hey C++ gurus!

The String class has the c_str() method to convert to a const char*. However, this means we have lots of duplicated APIs in the code, e.g.

void Spark.subscribe(const char* eventName, ...);

and

void Spark.subscribe(String eventName, ...) {
    Spark.subscribe(eventName.c_str(), ...);
}

With the String api simply unwrapping the string to a C-string and calling the C-string function.

We can add an implicit conversion to String, so that it can be used wherever a const char* is expected:

class String {
    // ...
    operator const char*() { return c_str(); }
}

This works well and allows us to eliminate the API duplicates and gives users more flexibility to use Strings or C-strings as they wish.

And now to the question! Anyone see any reason not to do this?

4 Likes

I’m new to Particle and found myself using c_str() without thinking about whether it was supported or not. As I was studying the firmware reference found here:

https://docs.particle.io/reference/firmware/photon/#string-class

I was surprised not to see c_str() included in the APIs. My question … is this a deliberate choice not to document it? Should I not use the c_str() function if I want to ensure portability and long-term support of my code?

c_str() is also a feature used on Arduinos and hence Particle won’t let it die - but I personally prefer the type cast version (const char*)someString as it can be used with both C-strings and String objects.
So refactoring a String into a C-string would not break existing code.

2 Likes

I was trying to figure out whether I should be using a string (const char *) or String in publish() and subscribe(). The compiler seemed not to care, but I was trying to pin this down…

In the reference docs, publish() mentions both explicitly: subscribe() just gives an example with a const char *. In the .h files, all I could find was const char *. I finally stumbled across this discussion, as well as evidence that it actually got implemented.

If not there already, this seems important enough to highlight in the docs, perhaps in the preamble to String – merely a note that whenever a const char * is required a String will work (unless I’ve got this totally wrong, in which case nvm😀)

I won’t stop advocating against the use of String due to its potential risk of causing heap fragmentation which may - over time - cause unexpected, hard to debug behaviour.

So whenever you can go with char*, const char*, char[] and const char[].

1 Like

Whenever you need a const c-string, there are two ways to get one from a String object:

  • operator const char *, which is what you get when you cast a String as a const char *
  • The method c_str(), which also returns a const char *.

For functions that take a const char * parameter, you don’t even need the cast, because the compiler will automatically use operator const char *.

The place where you get into trouble is when you pass a String to variable arguments, such as Log.info and sprintf. The compiler doesn’t know what type you want, so you need to explicitly cast as (const char *) or use the c_str() method.

1 Like

Yup, I just discovered that last bit while trying to print out a String. Is this just common knowledge? If not, I vote for putting it in the docs.

OK, that sounds like sage advice, thank you!