Can std::string::c_str() be used whenever a string literal is expected?

2k views Asked by At

I would guess that the last two lines in this code should compile.

#include "rapidjson/document.h"

int main(){
    using namespace rapidjson ;
    using namespace std ;

    Document doc ;
    Value obj(kObjectType) ;
    obj.AddMember("key", "value", doc.GetAllocator()) ; //this compiles fine
    obj.AddMember("key", string("value").c_str(), doc.GetAllocator()) ; //this does not compile!
}

My guess would be wrong, though. One line compiles and the other does not.

The AddMember method has several variants as documented here, but beyond that... why is the return of .c_str() not equivalent to a string literal?

My understanding was that where ever a string literal was accepted, you could pass string::c_str() and it should work.

PS: I'm compiling with VC++ 2010.

EDIT:
The lack of #include <string> is not the problem. It's already included by document.h

This is the error:

error C2664: 'rapidjson::GenericValue<Encoding> &rapidjson::GenericValue<Encoding>::AddMember(rapidjson::GenericValue<Encoding> &,rapidjson::GenericValue<Encoding> &,Allocator &)'
: cannot convert parameter 1 from 'const char [4]' to 'rapidjson::GenericValue<Encoding> &'
    with
    [
        Encoding=rapidjson::UTF8<>,
        Allocator=rapidjson::MemoryPoolAllocator<>
    ]
    and
    [
        Encoding=rapidjson::UTF8<>
    ]

EDIT2:
Please ignore the fact that .c_str() is called on a temporal value. This example is just meant to show the compile error. The actual code uses a string variable.


EDIT3:
Alternate version of the code:

string str("value") ;
obj.AddMember("key", "value", doc.GetAllocator()) ; //compiles
obj.AddMember("key", str, doc.GetAllocator()) ; // does not compile
obj.AddMember("key", str.c_str(), doc.GetAllocator()) ; // does not compile
4

There are 4 answers

3
D Drmmr On BEST ANSWER

Looking at the documentation you linked to, it seems like you are trying to call the overload of AddMember taking two StringRefTypes (and an Allocator). StringRefType is a typedef for GenericStringRef<Ch>, which has two overloaded constructors taking a single argument:

template<SizeType N>
GenericStringRef(const CharType(&str)[N]) RAPIDJSON_NOEXCEPT;

explicit GenericStringRef(const CharType *str);

When you pass a string literal, the type is const char[N], where N is the length of the string + 1 (for the null terminator). This can be implicitly converted to a GenericStringRef<Ch> using the first constructor overload. However, std::string::c_str() returns a const char*, which cannot be converted implicitly to a GenericStringRef<Ch>, because the second constructor overload is declared explicit.

The error message you get from the compiler is caused by it choosing another overload of AddMember which is a closer match.

1
Richard Hodges On

even if this code compiled:

obj.AddMember("key2", string("value").c_str(), doc.GetAllocator());

You cannot guarantee that it is safe.

The const char* returned by std::string::c_str() will be valid until the end of this statement.

If the AddMember method stores a copy of the string itself, all well and good. If it stores a pointer then you're doomed. You need knowledge of the inner workings of AddMember before you can reason about the correctness of your code.

I suspect the authors have already thought of this and have constructed overloads that demand that you either send in a std::string object (or equivalent) or a string literal reference (template<std::size_t N> void AddMember(const char (&str)[N]))

Even if this is not what they had in mind, they might be looking to protect you from yourself, in case you inadvertently send in an invalid pointer.

While seemingly an inconvenience, this compile time error indicates a possibly-faulty program. It's a tribute to the library's authors. Because compile time errors are a gazillion times more useful than runtime errors.

3
Cheers and hth. - Alf On

Re

why is the return of .c_str() not equivalent to a string literal

A string literal is a zero-terminated string in an array with size known at compile time.

c_str() produces a pointer to (the first item in) a zero-terminated string in an array with size known only at run-time.

Usually a string literal expression will be used in a context where the expression decays to pointer to first item, but in some special cases it does not decays. These cases include

  • binding to a reference to array,

  • using the sizeof operator, and

  • forming a larger literal by compile time concatenation of string literals (simply writing them in order).

I think that's an exhaustive list.


The error message you cite,

cannot convert parameter 1 from 'const char [4]' to 'rapidjson::GenericValue &

does not match your presented code

#include "rapidjson/document.h"

int main(){
    using namespace rapidjson ;
    using namespace std ;

    Document doc ;
    Value obj(kObjectType) ;
    obj.AddMember("key1", "value", doc.GetAllocator()) ; //this compiles fine
    obj.AddMember("key2", string("value").c_str(), doc.GetAllocator()) ; //this does not compile!
}

Nowhere in this code is there a three character long string literal.

Hence the claims that “this compiles” and “this does not compile”, are not very trustworthy.

You

  • should have quoted the actual error message and actual code (at least one of them is not what you had when you compiled), and

  • should have quoted the documentation of the function you're calling.

Also, note that the actual argument that compiler reacts to in the quoted diagnostic, is a literal or an array declared as such, not a c_str() call.

2
Dietmar Kühl On

The std::string::c_str() method returns a char const*. The type of a string literal is char const[N] where N is the number of characters in the string (including the null terminator). Correspondingly, the result of c_str() can not be used in all places where a string literal can be used!

I'd be surprised if the interface you are trying to call requires a char array, though. That is, in your use it should work. It is more likely that you need to include <string>.