Arduino String class implementation problem?

I don't know if there is a problem in the String class implementation or if I misunderstood something.

Here is a sketch for Arduino Nano ESP32 that I expect "hello world" to be printed twice, but "str1" is empty string:

void setup() {
  auto const& str1 = String("hello") + " " + String("world");
  auto const str2 = String("hello") + " " + String("world");
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println(str1);
  Serial.println(str2);
}
void loop() {
}

Here is the equivalent C++ that outputs "hello world" twice:

#include <iostream>

int main(int argc, char *argv[]) {
    auto const& str1 = std::string("hello") + " " + std::string("world");
    auto const str2  = std::string("hello") + " " + std::string("world");
    std::cout << str1 << std::endl;
    std::cout << str2 << std::endl;
}

Kind regards!

Looks to me like that's creating a reference to a Temporary String object that's created by 'String("hello") + " " + String("world");'
After that statement completes, that Temporary object is destroyed and the reference to it is no longer valid.

1 Like

This works though:
auto const& str1 = (String("hello") + " " + String("world")).c_str();

Is it a fluke that it works in the C++ example with std::string?

Might it be an error and referring to "freed" memory?

Kind regards!

Could be. Either way, doing that doesn't seem to be a very good idea.

1 Like

Indeed.

@calint - so what are you trying to achieve by that? What do you think it should do?

I hoped to be able to use "#define let auto const&" and consistently use "let" where an immutable reference/value is used. I hoped the compiler realizes that the temporary value created is referenced and should not be "freed".

Kind regards!

The C++ code:

#include <iostream>

struct Bar {
    Bar(const char* s) : str(s) {}
    ~Bar(){ std::cout << str << " destroyed\n"; }
    const char* str;
};

int main(int argc, char *argv[]) {
    auto const& bar1 = Bar("hello");
    std::cout << "----\n";
    auto const bar2 = Bar("world"); 
}

outputs:

----
world destroyed
hello destroyed

so the bar1 reference is valid after the line it was declared.

Might it be the compiler version? (g++ (Ubuntu 12.3.0-1ubuntu1~23.04) 12.3.0)

Kind regards!

Further investigation ... The sketch:

struct Bar {
  Bar(const char* s)
    : str(s) {}
  ~Bar() {
    Serial.print(str);
    Serial.println(" destroyed");
  }
  const char* str;
};

void setup() {
  auto const& str1 = String("hello");
  auto const str2 = String("world");
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println(str1);
  Serial.println(str2);

  auto const& bar1 = Bar("hello");
  Serial.println("----");
  auto const bar2 = Bar("world");
}

void loop() {
}

outputs:

hello
world
----
world destroyed
hello destroyed

so the temporary objects str1 and bar1 are valid after the line they were defined.

The sketch:

struct Bar {
  Bar(const char* s)
    : str(s) {}
  ~Bar() {
    Serial.print(str);
    Serial.println(" destroyed");
  }
  const char* str;
};

void setup() {
  auto const& str1 = String("hello") + "!";
  auto const str2 = String("world");
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println(str1);
  Serial.println(str2);

  auto const& bar1 = Bar("hello");
  Serial.println("----");
  auto const bar2 = Bar("world");
}

void loop() {
}

outputs:

world
world
----
world destroyed
hello destroyed

so something unexpected happens when adding "!" to str1

Kind regards!

That alone should be enough to tell you that you're flirting with Undefined Behavior. You should just give up what you're trying to do.

1 Like

I guess so :slight_smile:

Worth a try though considering the esthetics of the source with a ubiquitous "let" for any immutable reference/value. Might also automatically avoid making copies of objects when not necessary ...

Kind regards!

This is not true, the code is valid, because lifetime extension takes place, see Reference initialization - cppreference.com.

I agree that OP's code should work as expected, and something fishy is going on.

This, on the other hand, is not valid. str1 is a pointer to a string that will have been destroyed by the time you use it. Only the lifetime of the pointer is extended, which is not what you need.

To me, this is just unnecessary obfuscation.

Thanks for the "reference".

But in what situation would it actually be useful? Verses simply initializing a constant object of the data type?

In a generic function, you may not know whether something is a reference or a value. In that case, you might want to hold on to it in a variable, but not create unnecessary copies, either because of overhead of copying/moving, or because you might care about the address of the referenced object.

For example, if you have a user-provided function f, you'd use const auto &tmp = f();.
By binding the unknown return value to a const auto & or a universal auto &&, you won't copy it if f returns a reference.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.