6

I noticed that when I initialized a string, the compiler reported an error that I was not expecting.

For example:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s1 = "Hello", s2 = "World!"; // ok
    string s3 = s1 + ", " + "World!"; // ok
    string s4 = "Hello" + ", " + s2; // error
    cout << s1 + " " + s2 << endl; //ok
    return 0;
}

For me, if s3 worked just fine, s4 should do the same.

Why do I get that error? What is the difference between these two initialization strings (s3 and s4)?

Gabriel
  • 763
  • 1
  • 10
  • 28
  • 1
    This is just a little inconvenience of C++. You can use `std::string() + "Hello" + ...` to make it compile without significant penalty. – Neil Kirk Sep 13 '15 at 22:37
  • 6
    If you're coming from another language background, you will be shocked to learn that `"Hello"` is not a `string`. And that's the cause of all (not only) your trouble. – Walter Sep 13 '15 at 22:43
  • @NeilKirk that is kind of weird, but thank you for the information. Do you know why this happens? – Gabriel Sep 13 '15 at 22:43
  • @Walter now you really got my attention. Can you talk more about that? – Gabriel Sep 13 '15 at 22:45
  • 1
    Well it is a "string", it depends what you mean by "string". It is a C-style string stored in an array of chars, `const char[6]`. But it is not a `std::string` – Neil Kirk Sep 13 '15 at 22:46
  • Thank you for your explanation @NeilKirk , you and Walter helped me a lot. – Gabriel Sep 13 '15 at 22:51
  • To concatenate two string literals, just write them next to each other. `string s4 = "Hello" ", " + s2;` – T.C. Sep 13 '15 at 23:06
  • Thank you for the suggestion and also for the edited post @T.C. ! – Gabriel Sep 13 '15 at 23:15
  • An additional quirk is that `string s4 = "Hello" ", " + s2;` **without** the first `+` works, because the preprocessor will concatenate adjacent string literals. – Bo Persson Sep 14 '15 at 06:51

5 Answers5

13

"Hello" is not a std::string (rather it is a const char[6], while ", " is a const char[3]) and the + operator for std::strings does not apply.

This is a minor inconvenience of C++ and stems from its C ancestry. It means that in concatenating strings via + you must ensure that at least one of its two operands actually is a std::string, as in

auto s = std::string{"Hello"} + ", " + "World";

where the first + has a std::string as its first operand and hence produces a std::string, so that the second + also has a std::string as its first operand (since + is processed from left to right).


Edit1 prompted by the comment by T.C., I mention that C-string literals are automatically concatenated if only separated by white space:

std::string s = "Hello" ", " "World";

This behaviour too is inherited from C: the preprocessor renders above code to

std::string s = "Hello, World";

before the compiler proper processes it (to be more precise, string concatenation takes place in phase 6 of translation, just before compilation). This is in fact the most simple and hence most convenient way to concatenate raw string literals. But note that I had to declare the type of s, since auto deduction would have given a const char*.


Edit2 prompted by the comment by PaperBirdMaster, I mention that since C++14, you can directly form std::string literals by simply adding s after the string, if you pull in the associated operator""s (or surrounding namespace).

using std::literals::operator""s;                // pull in required operator
const auto s = "Hello"s + ", " + "World";        // std::string

See this post as to why the required operator""s is hidden in the nested namespace. Note also that alternatively to using std::literals::operator""s; you may pull in the surrounding namespace: either of the following declarations will do.

using namespace std::string_literals;
using namespace std::literals::string_literals;
using namespace std::literals;

which are not as bad (and damned) as plain using namespace std;.

Community
  • 1
  • 1
Walter
  • 44,150
  • 20
  • 113
  • 196
  • That explains a lot, in a very clear and fast way. Thank you for that, @Walter ! – Gabriel Sep 13 '15 at 22:50
  • Or, you know, drop the `+` and let string literal concatenation work its magic :) – T.C. Sep 13 '15 at 22:52
  • I think that is worth to mention the use of UDL, the following is valid: `string s4 = "Hello"s + ", " + s2;` as long as we are `using namespace std::string_literals;` (C++14 only) – PaperBirdMaster Sep 14 '15 at 06:56
  • @bku_drytt My answer is/was correct, though for the unaware this may not be obvious. The point is that the `operator+` has exactly 2 arguments, at least one of which must be a `std::string`. I clarified the answer as to what operand means in the example code. – Walter Sep 14 '15 at 08:12
  • As for concatenating raw string literals, is done in "Phase 6" of translation, [here](http://en.cppreference.com/w/cpp/language/translation_phases) we have all the phases explained. – PaperBirdMaster Sep 14 '15 at 08:38
  • @PaperBirdMaster Yes, phase 6 is still preprocessing in the broader sense (than just phase 4). I added a remark in brackets. – Walter Sep 14 '15 at 08:42
6

Strings and C-strings

In C++, strings are usually represented as either std::string or a C-string which has type char[N], where N is the number of characters in the C-string plus one for the termination character (or null character) \0.

When passing a string literal as, e.g., "Hello" to a function, you are actually passing a char[6], which will decay into a pointer char*, as C++ does not allow you to pass arrays by value. Additionally, C++ does not allow non-const pointers to string literals, and the parameter type must therefore become const char*.

Concatenation

std::string has existing overloads for the binary addition operator which allows you to concatenate two std::string objects into one, as well as concatenate a std::string object with a C-string.

std::string s1 = "Hello";
std::string s2 = "World";

// Uses 'std::string operator+(const std::string&, const std::string&)'.
std::string s3 = s1 + s2;

// Uses 'std::string operator+(const std::string&, const char*)'.
std::string s4 = s1 + "World";

// Also ok.
std::string s4 = "Hello" + s2;

There is however, no overload for concatenating two C-strings.

// Error. No overload for 'std::string operator+(const char*, const char*)' found.
std::string s5 = "Hello" + "World";

From left to right

Addition is performed left to right in C++, and hence, long concatenation expressions need to start with a std::string object.

std::string s6 = "Hello" + ", " + s2; // Error. Addition is performed left-to-right and
                                      // you can't concatenate two C-strings.

std::string s7 = s1 + ", " + "World"; // Ok. 's1 + ", "' returns a new temporary
                                      // 'std::string' object that "World" is concat'ed to.

A simple solution

Using C++14 standard literals, you can create std::string objects from literal expressions (note the trailing s in the literal expression).

using namespace std::literals;
std::string s8 = "Hello"s + ", " + "World"; // Ok

Alternatively you can use the fact that the preprocessor will automatically concatenate literal strings separated with whitespace.

std::string s9 = "Hello" ", " "World"; // Ok

But then, you could just as well skip the whitespace altogether and use a single string literal.

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
5

The following line attempts to add 2 pointers together.

string s4 = "Hello" + ", " + s2; // error

"Hello" is a const char[6] which decays to const char* and ", " is a const char[3] which decays to const char*.

You are trying to assign the addition of two pointers to a std::string, which is not valid. Furthermore, the addition of two pointers is not valid either.

As other people have said, this doesn't have anything to do with the overloads of operator+ of the std::string class, as the following code would also not compile for the same reason:

string s4 = "Hello" + ", ";

I have added some clarifications provided by user @dyp.

You can fix your problem by using the C++ Standard Library "user-defined" string literal. The functionality is provided by std::literals::string_literals::operator""s which is found in the <string> header. You might have to use a using-directive or -declaration; this was not required on my compiler and including the <string> header was enough. Example:

string s4 = "Hello"s + ", "s + s2;

This is the equivalent of:

string s4 = std::string("Hello") + std::string(", ") + s2;

When you place an instance of std::string as the first operand, the rest of the expression will use that type, because of left to right associativity for operator+ will make sure that an instance of std::string is returned as the left-most operand every time, instead of const char*. That is why you have no error on line:

string s3 = s1 + ", " + "World!";

That line is equivalent to:

string s3 = ((s1 + ", ") + "World!");
bku_drytt
  • 3,169
  • 17
  • 19
  • Thank you for that @bku_drytt , now your answer is clear for me! – Gabriel Sep 13 '15 at 22:54
  • Thank you for your comment, I have updated my answer to include your knowledge. – bku_drytt Sep 13 '15 at 23:09
  • Both `std::literals` and `std::literals::string_literals` are *inline namespaces*, so a using-directive `using namespace std;` is sufficient to make this `operator""s` visible. – dyp Sep 14 '15 at 08:28
3

The first thing to understand is that due to its ancestry in C, a string literal like "Hello" in C++ is not actually of type string. Its type is actually an array of characters instead.

So the difference between s3 and s4 is that when you do the s3 construction, the first operation done is s1 + ", ". s1 is of type string, so the + operator provided by the string component of the standard library is used, and returns a new string to be added to "World!".

Now for s4 the first operation to be performed is "Hello" + ", " or the addition of two string literals. This operation would actually been see to the compiler as adding two character arrays together, a capability not provided by the language, thus resulting in the error you see.

The simplest fix is to just not use + for string literals, just put them all together in the same string. Alternately you could create a string from the first literal and let the operation work from there (std::string("Hello") + ", " + s2)

Mark B
  • 95,107
  • 10
  • 109
  • 188
2

The + operator is overloaded in std::string. Operator overloads are applied to the operands, so s1 + ... works by calling the overloaded + operator on s1.

Literal "Hello" is a const char * and so is ",". const char * does not have an overloaded + operator, hence the error. Try string("Hello") + ... and it will work.

Thanks for the correction @interjay

Ali
  • 1,462
  • 2
  • 17
  • 32
  • 5
    Operator overloads are not applied only to the left operand. There is also a `const char * + std::string` overload. The problem here is attempting to add two `const char *`. – interjay Sep 13 '15 at 22:31
  • What @interjay said. `string s4 = "Hello" + s2;` would also work. – juanchopanza Sep 13 '15 at 22:32
  • 3
    `"Hello"` is not a `const char *`. It's a `const char[6]`, which can decay to `const char *`. – Emil Laine Sep 13 '15 at 23:00