Why Doesn't Std::String Provide Implicit Conversion to Char*

Why doesn't std::string provide implicit conversion to char*?

From the C++ Programming Language 20.3.7 (emphasis mine):

Conversion to a C-style string could have been provided by an operator const char*() rather than c_str(). This would have provided the convenience of an implicit conversion at the cost of surprises in cases in which such a conversion was unexpected.

Why const char* implicitly converted to bool rather than std::string?

Because the implicit conversion from const char* to bool is qualified as standard conversion, while const char* to std::string is user-defined conversion. The former has higher ranking and wins in overload resolution.

A standard conversion sequence is always better than a user-defined conversion sequence or an ellipsis conversion sequence.

BTW: mystruct obj(c); performs direct initialization, explicit converting constructors including mystruct::mystruct(bool) are considered too. As the result, c is converted to bool then passed to mystruct::mystruct(bool) as argument to construct obj.

Direct-initialization is more permissive than copy-initialization: copy-initialization only considers non-explicit constructors and non-explicit user-defined conversion functions, while direct-initialization considers all constructors and all user-defined conversion functions.

About explicit specifier,

  1. Specifies that a constructor or conversion function (since C++11) or deduction guide (since C++17) is explicit, that is, it cannot be used for implicit conversions and copy-initialization.

Why can't the std::string constructor take just a char?

In C and C++, char serves double-duty. It represents both a character and a number. It is considered an integral type, which means that it participates in implicit integer promotion to many other integral types, as well as implicit conversion from other integral types.

Because char is overloaded, it is impossible at the level of a function's interface to know if a user passed in an actual character (a character literal or a variable of type char) or a numeric literal that just so happens to be small enough to fit into a char.

As such, when using char in an interface, one must be careful of accidental conflicts with what the user is providing.

Sequence container types (types that hold a number of elements in a sequence unrelated to the values of those elements) usually have a constructor that takes a count of Ts. This is the number of elements to create in the sequence, constructed via value initialization (there is also has a version that takes a T which is used to copy-initialize these elements, which is what you used).

But basic_string is special; it doesn't have such a constructor. If you tried to do std::string s(4);, you would get a compile error.

However, if you make the change you want, std::string s(4); would compile and execute. But it would not give you a sequence of 4 value-initialized characters. It would give you a string containing a single character with a value of 4. This is because the integer literal 4 can be converted to a char implicitly.

It's bad enough that basic_string is not consistent with the expectations of the common sequence container interface. But to actively make it compile but have radically different behavior would be way worse.

Furthermore, you can use list initialization to get what you want more explicitly:

std::string s1{some_char};
std::string s2 = {4}; //converts 4 to `char`
std::string s3 = {other_char};

List initialization is how we initialize containers of T with a sequence of Ts.

Is initializing char array from string literal considered to be a implicit conversion?

Language-lawyerly speaking, initializing char array from string literal is a implicit conversion.

[conv.general]:

An expression E can be implicitly converted to a type T if and only if the declaration T t=E; is well-formed, for some invented temporary variable t ([dcl.init]).

Note that the core language only defines implicit conversion from an expression to a type. So the meaning of "implicit conversion from const char[7] to char[10]" is undefined.

is_convertible<From, To>::value is false whenever To is an array type, because it is defined to produce false if To is not a valid return type, which an array is not. (This can be implemented in different ways.)

[meta.rel]/5:

The predicate condition for a template specialization is_­convertible<From, To> shall be satisfied if and only if the return expression in the following code would be well-formed, including any implicit conversions to the return type of the function:

To test() {
return declval<From>();
}

Arrays can rarely be the destination of implicit conversions, since they can be neither parameter types nor return types. But they are not excluded from temporary materialization conversion.

Implementing a String class with implicit conversion to char* (C++)

If I understood you correctly, you want this to work:

mystring foo;
c_function(foo);
// use the filled foo

with a c_function like ...

void c_function(char * dest) {
strcpy(dest, "FOOOOO");
}

Instead, I propose this (ideone example):

template<std::size_t max>
struct string_filler {
char data[max+1];
std::string & destination;
string_filler(std::string & d) : destination(d) {
data[0] = '\0'; // paranoia
}
~string_filler() {
destination = data;
}
operator char *() {
return data;
}
};

and using it like:

std::string foo;
c_function(string_filler<80>{foo});

This way you provide a "normal" buffer to the C function with a maximum that you specify (which you should know either way ... otherwise calling the function would be unsafe). On destruction of the temporary (which, according to the standard, must happen after that expression with the function call) the string is copied (using std::string assignment operator) into a buffer managed by the std::string.


Addressing your questions:

Do you think there are any major pros/cons of using a vector instead of char* here?

Yes: Using a vector frees your from manual memory management. This is a huge pro.

I plan to add wide char support to it later. Do you think a union of two structs : {char,string} and {wchar_t, wstring} would be the way to go for that purpose (it will be only one of these two at a time)?

A union is a bad idea. How do you know which member is currently active? You need a flag outside of the union. Do you really want every string to carry that around? Instead look what the standard library is doing: It's using templates to provide this abstraction.

Is it too much overkill [..]

Writing a string class? Yes, way too much.

Implicit conversion from char to int for constructors in C++

But C++ standard allows only 1 implicit conversion.

Thats not correct.

From cppreference:

Implicit conversion sequence consists of the following, in this order:

  1. zero or one standard conversion sequence;
  2. zero or one user-defined conversion;
  3. zero or one standard conversion sequence.

From the language point of view, const char[N] -> std::string (or const char* to std::string) is a user-defined conversion. Hence, the commented out lines are errors. On the other hand,

A obj_5 = ch;               // How is this working?

is fine, because there is only a single user-defined conversion involved.

std::string implicit conversion priority, string_view over const char*

std::string has a non-explicit conversion operator to std::string_view, so it could convert to std::string_view implicitly. But std::string doesn't have a conversion operator to const char*, it can't convert to const char*. You have to call its c_str() method explicitly to get a const char*:

Set(some_string.c_str());


Related Topics



Leave a reply



Submit