41

Const-correctness in C++ is still giving me headaches. In working with some old C code, I find myself needing to assign turn a C++ string object into a C string and assign it to a variable. However, the variable is a char * and c_str() returns a const char []. Is there a good way to get around this without having to roll my own function to do it?

edit: I am also trying to avoid calling new. I will gladly trade slightly more complicated code for less memory leaks.

Jamison Dance
  • 19,896
  • 25
  • 97
  • 99

14 Answers14

33

C++17 and newer:

foo(s.data(), s.size());

C++11, C++14:

foo(&s[0], s.size());

However this needs a note of caution: The result of &s[0]/s.data()/s.c_str() is only guaranteed to be valid until any member function is invoked that might change the string. So you should not store the result of these operations anywhere. The safest is to be done with them at the end of the full expression, as my examples do.


Pre C++-11 answer:

Since for to me inexplicable reasons nobody answered this the way I do now, and since other questions are now being closed pointing to this one, I'll add this here, even though coming a year too late will mean that it hangs at the very bottom of the pile...


With C++03, std::string isn't guaranteed to store its characters in a contiguous piece of memory, and the result of c_str() doesn't need to point to the string's internal buffer, so the only way guaranteed to work is this:

std::vector<char> buffer(s.begin(), s.end());
foo(&buffer[0], buffer.size());
s.assign(buffer.begin(), buffer.end());

This is no longer true in C++11.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
sbi
  • 219,715
  • 46
  • 258
  • 445
  • 1
    +1 even though I'm quite partial to my answer here :) http://stackoverflow.com/questions/4317318/how-do-i-pass-an-stdstring-to-a-function-that-expects-char/4317427#4317427 – John Dibling Nov 30 '10 at 21:02
  • 1
    Is buffer NUL terminated or do I need to add that? – nitzanms May 17 '16 at 15:08
  • 1
    @nitzanms: That depends on `foo()`, of course. (Note, however, that `std::string` treats `'\0'` as any character. While the result of invoking `std::string::c_str()` is guaranteed to be zero-terminated, internally `std::string` does not necessarily need to be zero-terminated, and you must not include the `'\0'` when assigning the vector content back to the string.) – sbi May 17 '16 at 19:28
  • 2
    This Q/A is really old, though IMHO nice and voted. May be, it's worth to mention that things have changed with C++11: _The elements of a basic_string are stored contiguously, that is, for a basic_string s, &*(s.begin() + n) == &*s.begin() + n for any n in [0, s.size()), or, equivalently, a pointer to s[0] can be passed to functions that expect a pointer to the first element of a CharT[] array._ (cited from [cppreference: std::basic_string](https://en.cppreference.com/w/cpp/string/basic_string)) – Scheff's Cat Oct 19 '18 at 08:40
  • @Scheff: I have made the answer CW. Feel free to add to it. – sbi Oct 31 '18 at 16:31
17

There is an important distinction you need to make here: is the char* to which you wish to assign this "morally constant"? That is, is casting away const-ness just a technicality, and you really will still treat the string as a const? In that case, you can use a cast - either C-style or a C++-style const_cast. As long as you (and anyone else who ever maintains this code) have the discipline to treat that char* as a const char*, you'll be fine, but the compiler will no longer be watching your back, so if you ever treat it as a non-const you may be modifying a buffer that something else in your code relies upon.

If your char* is going to be treated as non-const, and you intend to modify what it points to, you must copy the returned string, not cast away its const-ness.

Joe Mabel
  • 1,372
  • 10
  • 29
  • 1
    I'm in favour in preserving the `const` correctness of code, and avoid placing a manual management burden on programmers to "get it right". So it's safer to make a copy, and pass that pointer to the legacy code. Or, fix the `const` usage in the legacy code if at all possible. – Craig McQueen Sep 13 '13 at 06:06
  • Wow. A downvote for correctly answering the question, because you don't like doing what the questioner wanted to do? – Joe Mabel Sep 14 '13 at 16:52
14

I guess there is always strcpy.

Or use char* strings in the parts of your C++ code that must interface with the old stuff.

Or refactor the existing code to compile with the C++ compiler and then to use std:string.

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • 1
    Refactoring the existing code is a little out of the question right now. It is around 10k lines of hardcore, scientific C implementing a fairly complicated algorithm. However, I am pleased there is a simple answer I didn't think of. – Jamison Dance Dec 17 '09 at 05:39
  • 2
    If you are avoiding new (as you say in another comment), you should probably consider using `strncpy` to avoid over running your stack or static allocated buffer...but then you'll have to deal with the std::string too long for buffer case another way. – dmckee --- ex-moderator kitten Dec 17 '09 at 05:45
  • 1
    No need for strncpy. If you're copying from a std::string you know how long that is. Good advice doesn't hold everywhere. – Alex Budovski Dec 17 '09 at 06:23
  • ... and sometimes you need to deal with shitty APIs like win32. – Shahbaz Jan 04 '17 at 18:12
12

There's always const_cast...

std::string s("hello world");
char *p = const_cast<char *>(s.c_str());

Of course, that's basically subverting the type system, but sometimes it's necessary when integrating with older code.

mch
  • 7,155
  • 3
  • 30
  • 34
  • Are there any strange side-effects to using const_cast (besides now being able to modify the c string somewhere else in the code)? – Jamison Dance Dec 17 '09 at 05:35
  • 1
    I don't think the standard currently requires contiguous storage, so make sure your implementation uses it. (It likely does). The next standard will require it, AFAIK. – GManNickG Dec 17 '09 at 05:39
  • 1
    You have to be careful, very careful, not to go off the end of the memory allocated for the string. If you modify the contents of the resulting pointer, you are modifying the internal buffer of the std::string. If you attempt to use the pointer after the std::string object goes out of scope, there will be trouble. It's only something to be used in a very limited scope when you absolutely have to (i.e. within say 10 lines of the const_cast). Don't take the resulting char* and pass it to other functions, return it, store it as a member variable, etc. Make a copy if you have to do that. – mch Dec 17 '09 at 05:40
  • Wait . . . `const_cast` **removes** the `const` qualification? Who picked the name for such a cast. – dreamlax Dec 17 '09 at 05:45
  • `const_cast` modifies the `const`-ness of it's argument. – GManNickG Dec 17 '09 at 05:50
  • 1
    There aren't any strange side effects to using const_cast. The assembly code generated can, in certain cases, not be as efficient because the compiler can't assume the string won't be changed. The main time I have used const_cast in this context is to pass strings to legacy C libraries in which the programmer didn't use 'const'. I re-iterate mch's 'careful'. – mcdave Dec 17 '09 at 05:51
  • 7
    -1. You will get undefined behavior here when you will try to modify const object. – Kirill V. Lyadvinsky Dec 17 '09 at 08:57
  • `const_cast` is often a bad idea. In this particular case, if you modify the internal buffer (without overrunning), how can you make sure that... the length is still correct ? Most of the string implementations I have seen buffered the length variable (so that .size() is O(1) and not O(n)). – Matthieu M. Dec 17 '09 at 10:00
  • 5
    Modifying the characters pointed to by p is undefined behavior - if the reason the cast is needed is because the function being called just neglected to mark its parameter const, that might be fair enough. If the reason a non-const pointer is needed is because the function being called modifies the string, then it isn't fair enough. – Steve Jessop Dec 17 '09 at 12:06
  • Many comments assume `c_str()` a.k.a. `data()` points to "the internal buffer" (whatever that's meant to mean!) which is not defined or required at all, at least not until C++17 with its non-const `data()`. Some other comments assume OP is going to modify via the `const_cast`ed pointer - and are very vocal about having downvoted based upon that unfounded assumption. That wasn't specified by OP at all. If the recipient of the resulting pointer doesn't write via it, and the `string` doesn't contain `NUL`s, die or get modified before the recipient reads via said pointer again - there's no problem – underscore_d Sep 13 '16 at 21:56
7

If you can afford extra allocation, instead of a recommended strcpy I would consider using std::vector<char> like this:

// suppose you have your string:
std::string some_string("hello world");
// you can make a vector from it like this:
std::vector<char> some_buffer(some_string.begin(), some_string.end());
// suppose your C function is declared like this:
// some_c_function(char *buffer);
// you can just pass this vector to it like this:
some_c_function(&some_buffer[0]);
// if that function wants a buffer size as well,
// just give it some_buffer.size()

To me this is a bit more of a C++ way than strcpy. Take a look at Meyers' Effective STL Item 16 for a much nicer explanation than I could ever provide.

Dmitry
  • 6,590
  • 2
  • 26
  • 19
  • 4
    warning: this code doesn't look to result in a null terminated char* going to some_c_function. I had to add some_buffer.push_back(0); as well – JasonZ Dec 22 '17 at 04:59
6

You can use the copy method:

len = myStr.copy(cStr, myStr.length());
cStr[len] = '\0';

Where myStr is your C++ string and cStr a char * with at least myStr.length()+1 size. Also, len is of type size_t and is needed, because copy doesn't null-terminate cStr.

Juan
  • 3,433
  • 29
  • 32
6

Just use const_cast<char*>(str.data())

Do not feel bad or weird about it, it's perfectly good style to do this.

It's guaranteed to work in C++11. The fact that it's const qualified at all is arguably a mistake by the original standard before it; in C++03 it was possible to implement string as a discontinuous list of memory, but no one ever did it. There is not a compiler on earth that implements string as anything other than a contiguous block of memory, so feel free to treat it as such with complete confidence.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
Anne Quinn
  • 12,609
  • 8
  • 54
  • 101
  • [From cppreference](https://en.cppreference.com/w/cpp/string/basic_string/c_str), _"Writing to the character array accessed through c_str() is undefined behavior."_ – Eric Apr 09 '19 at 00:56
2

If you know that the std::string is not going to change, a C-style cast will work.

std::string s("hello");
char *p = (char *)s.c_str();

Of course, p is pointing to some buffer managed by the std::string. If the std::string goes out of scope or the buffer is changed (i.e., written to), p will probably be invalid.

The safest thing to do would be to copy the string if refactoring the code is out of the question.

Matt Davis
  • 45,297
  • 16
  • 93
  • 124
2
std::string vString;
vString.resize(256); // allocate some space, up to you
char* vStringPtr(&vString.front());
// assign the value to the string (by using a function that copies the value).
// don't exceed vString.size() here!
// now make sure you erase the extra capacity after the first encountered \0.
vString.erase(std::find(vString.begin(), vString.end(), 0), vString.end());
// and here you have the C++ string with the proper value and bounds.

This is how you turn a C++ string to a C string. But make sure you know what you're doing, as it's really easy to step out of bounds using raw string functions. There are moments when this is necessary.

CodeAngry
  • 12,760
  • 3
  • 50
  • 57
  • What if the temporary string changes as result of `wcstok_s`, does it get cleaned up? Also if we don't allocate it and assign it to `vString = original_string, does it clean up can we clean up the same way? – zar Oct 28 '19 at 21:12
1

If c_str() is returning to you a copy of the string object internal buffer, you can just use const_cast<>.

However, if c_str() is giving you direct access tot he string object internal buffer, make an explicit copy, instead of removing the const.

Franci Penov
  • 74,861
  • 18
  • 132
  • 169
  • To choose between these options, the user must be able to probe, and then is forced to rely upon, implementation details of whichever stdlib they're currently using. Such details might change in the next version of the library or just the next time the program is run. Basing anything on implementation details is always a terrible idea, as it defeats the purpose of the library: to provide a standard interface, not implementation. – underscore_d Sep 11 '16 at 08:24
  • True. The correct always working answer would be to always make a copy. However, the OP explicitly stated they would gladly trade more complex code to avoid extra allocation. – Franci Penov Sep 12 '16 at 16:33
  • Not complexity I'm concerned about - more the brittleness of relying on assumptions about `c_str()`'s pointee and whether it happens to be the 'main' buffer. Per _cppreference_, it `Returns a pointer to a null-terminated character array with data equivalent to those stored in the string.` It says nothing of the identity of that array, only that it has an equivalent value if the `string` doesn't have internal `NUL`s, isn't modified in the interim and is never written via said pointer. Assuming that means it points at the same buffer is overreaching: if the Standard meant that, it would say that – underscore_d Sep 13 '16 at 10:41
  • True. On the other hand, probing what the exact behavior is can be rather easy at runtime, so the client app could check what the specific runtime does any time it starts, and tailor its implementation to that behavior. – Franci Penov Sep 13 '16 at 19:32
  • I guess, but that's overhead at start, & I'm not sure why. To me, you have the right options, but the wrong reason. It'd help if OP said whether they want to modify the resulting `char`s. A `const_cast` is ideal if we can guarantee it's safe - not by implementation, rather in use - i.e. (A) the recipient (has bad API) will never modify via the pointer & (B) the `string` won't be modified while the recipient still holds a (now formally invalid) pointer previously given out by it. If those guarantees aren't known, then indeed, take a copy and work on that. Btw, C++17 has non-`const` `.data()`... – underscore_d Sep 13 '16 at 21:41
  • ...which, if you believe this GCC commit, is not just for lazy convenience when passing to crappy APIs that behave `const` but don't mark themselves as such - rather, "Modifying the characters in the sequence is allowed." ...interesting. https://gcc.gnu.org/ml/libstdc++/2016-07/txtCyA100690X.txt Anyway! I'll stop thinking aloud on your post now. ;) – underscore_d Sep 13 '16 at 21:53
  • Yes, probing for the actual behavior is overhead at the start. Let me repeat though - the OP did state they are willing to accept complexity in order to avoid an additional copy. The cost of probing is a once time cost at startup (and it's not that big of a cost to start with, but let's not argue that point - it is a cost nevertheless :-)). The cost of making additional copy is paid every time the OP needs to perform that cast, which could be a rather serious cost depending on what is the business logic that needs it. – Franci Penov Sep 14 '16 at 17:08
  • And yes, it would be nice if the OP actually said why they need to make the cast and whether they will be modifying the string or not. But they didn't. And with the problem postulated the way it is... :-) – Franci Penov Sep 14 '16 at 17:08
1

Since c_str() gives you direct const access to the data structure, you probably shouldn't cast it. The simplest way to do it without having to preallocate a buffer is to just use strdup.

char* tmpptr;
tmpptr = strdup(myStringVar.c_str();
oldfunction(tmpptr);
free tmpptr;

It's quick, easy, and correct.

WarrenB
  • 2,145
  • 3
  • 14
  • 18
1

In CPP, if you want a char * from a string.c_str() (to give it for example to a function that only takes a char *), you can cast it to char * directly to lose the const from .c_str()

Example:

launchGame((char *) string.c_str());
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Lal
  • 151
  • 4
  • 8
  • You *can*, but *shouldn't*. `const char` means the variable can't/won't be changed. True, you often can change it anyway, much hair pulling ensues. – vonbrand Mar 19 '20 at 22:46
  • But const isn't just a way to label variables as "non changing" within my code ? Or does it change something in my asm ? – Lal Apr 01 '20 at 13:45
0

C++17 adds a char* string::data() noexcept overload. So if your string object isn't const, the pointer returned by data() isn't either and you can use that.

thakis
  • 5,405
  • 1
  • 33
  • 33
-4

Is it really that difficult to do yourself?

#include <string>
#include <cstring>

char *convert(std::string str)
{
    size_t len = str.length();
    char *buf = new char[len + 1];
    memcpy(buf, str.data(), len);
    buf[len] = '\0';
    return buf;
}

char *convert(std::string str, char *buf, size_t len)
{
    memcpy(buf, str.data(), len - 1);
    buf[len - 1] = '\0';
    return buf;
}

// A crazy template solution to avoid passing in the array length
// but loses the ability to pass in a dynamically allocated buffer
template <size_t len>
char *convert(std::string str, char (&buf)[len])
{
    memcpy(buf, str.data(), len - 1);
    buf[len - 1] = '\0';
    return buf;
}

Usage:

std::string str = "Hello";
// Use buffer we've allocated
char buf[10];
convert(str, buf);
// Use buffer allocated for us
char *buf = convert(str);
delete [] buf;
// Use dynamic buffer of known length
buf = new char[10];
convert(str, buf, 10);
delete [] buf;
Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • I'm trying to avoid calling new. Edited the original question to make that more clear. – Jamison Dance Dec 17 '09 at 05:41
  • @Jergason - I did provide you with a way to pass in your own (stack allocated) buffer, thus completely avoiding the call to `new`. And @GMan - Why would I use a `std::vector` when the question calls for a `char *` ? – Chris Lutz Dec 17 '09 at 05:43
  • 1
    You should pass a size parameter into convert if you are passing in a stack allocated buffer (or any buffer for that matter). Then use strncpy to avoid going off the end of the destination buffer. – mch Dec 17 '09 at 05:47
  • 2
    @Chris: So you don't have to worry about memory management and exceptions? A `vector` works just as well. `&v[0]` is your pointer, and the code ends up being cleaner without explicit memory management. – GManNickG Dec 17 '09 at 05:51