Can I get a non-const C string back from a C++ string?

Viewed 32259

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.

14 Answers

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.

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.

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.

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.

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.

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.

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.

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.

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());

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.

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;
Related