John Z. Li
John Z. Li

Reputation: 1995

How can I get a pointer to the first element of an std::string even if it is empty

How can I get a pointer to the first element of an non-const std::string even if it is empty.

I know for sure the string has a capacity greater than one, however, simply using

&my_string.front()

triggers a "out of range iterator" assertion failure because the string is empty. Using member function

data()

doesn't fit too, because I want a pointer of type char*, not const char*. I guess I can perform a const_cast here, but that doesn't seem neat.

Is there a better way to do this?

Upvotes: 3

Views: 2023

Answers (2)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385204

Although front() will throw here, my_string[my_string.size()] is nowadays deemed valid — you can't do much with it, but you can take and store its address (so, &my_string[0]).

However, there's only questionable value in doing so. So you could probably assume that the first bunch of character insertions (up to the current capacity) won't practically invalidate your pointer, but your code will be of unclear correctness, as we are specifically told by string.require¶4 not to rely on it:

References, pointers, and iterators referring to the elements of a basic_­string sequence may be invalidated by the following uses of that basic_­string object:

  • as an argument to any standard library function taking a reference to non-const basic_­string as an argument.
  • Calling non-const member functions, except operator[], at, data, front, back, begin, rbegin, end, and rend.

So it's really not assured that the special "empty" buffer pointed-to will really be the same one eventually used to store a real string (though that buffer will also be guaranteed null-terminated in the same way). Certainly once you go above the capacity all bets are completely off, and do you really want to be tracking that event?

It really sounds like std::string is not what you want, or that you should be accessing data() as and when rather than calling it once then storing the resulting pointer.

If you're interacting with some C API that needs to store this pointer, I would switch to something else, perhaps a fixed-length character buffer. On the C++ side you can still make use of std::string_view to do pleasing (read-only) things with your C-like character buffer, so you wouldn't lose any of that.

#include <string_view>
#include <iostream>

void OkayCApiStoreThisThen(const char*) {}

static constexpr const size_t BUF_SIZE = 255;
char silly_buf[BUF_SIZE] = {};

int main()
{
    // Sadface
    OkayCApiStoreThisThen(silly_buf);

    // Happyface
    std::string_view str(silly_buf, sizeof(silly_buf));

    silly_buf[0] = 'l';
    silly_buf[1] = 'o';
    silly_buf[2] = 'l';
    silly_buf[3] = '!';

    std::cout << str.substr(0, 3) << '\n';
}

Upvotes: 3

Krzysztof Lewko
Krzysztof Lewko

Reputation: 1002

To have string::data() returning char* you have to upgrade to C++17, unfortunately. const_cast'ing result of data() might be viable option if you really need to have write access to underlying data.

Upvotes: 4

Related Questions