2

Ben Saks in his lesson "Understanding Value Categories" at the 2019 CppCon in Aurora (CO) (great lesson btw) said:

"Character string literals, such as "examplestring", are lvalues because they are basically arrays and they need to be stored in memory."

At the end of the lesson a person ask this question to Ben: "Did you really mean that string literals are lvalues? because, if they are, why I can't assign to a string literal?" He answered "yes, because they act like array and they need a place in memory"

This confused me a little bit

So I have some questions:

  1. If the only reason why string literals are lvalues is that, why they cannot be temporary rvalues? During that lesson he explained that if an rvalue is too big (such as very long integer literals) the compiler store that in memory but that doesn't mean that they are lvalues...

  2. Why can I pass a string literal as a rvalue reference?

  3. std::string a = "string1"+"string2"; if "string1" and "string2" are lvalues how their sum can be an rvalue?

There something that I have missed because, in conclusion, everything would have been right for me if he had said that string literals are xvalues because as array they must have a location in memory but they can't be lvalues because they don't have a "name" and I can't reach them in memory because I'm not able to know the address (ex: int a => &a if I want to reach it)

YouTube link for the lesson: https://youtu.be/XS2JddPq7GQ?si=5aCMj1sm4YKHMPW-

Thank you in advance for your answers.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    What does 3. even mean? Sums of lvalues can certainly be rvalues, and in most cases they are. – Weijun Zhou Aug 30 '23 at 09:07
  • (2) Rvalue reference to *what type*? (3) This doesn't compile. You understand that string literals have type `const char[N]`, not `std::string`, right? – HolyBlackCat Aug 30 '23 at 09:14
  • thanks @HolyBlackCat for the answer: Yes sorry, it would have been better to write: std::string c = "Ciao"; c = c + "string2"; // the + operator return a rvalue right? isn't "string2" an rvalue and c al lvalue? – ofkilmurray Aug 30 '23 at 09:29
  • Don't trust random youtube for lessons... – Swift - Friday Pie Aug 30 '23 at 09:30
  • @nielsen no, in that thread my question is not answered, i would comment there but I can't because I am new in stack overflow so i opened a new thread – ofkilmurray Aug 30 '23 at 09:32
  • @ofkilmurray In `c + "string2"`, `c` is lvalue, `"string2"` is lvalue, `c + "string2"` is rvalue. – songyuanyao Aug 30 '23 at 09:34
  • @songyuanyao Result of expression `c + "string2"` is a prvalue, it's a result of overloaded operator, which returns a non-reference. Which may be bound to rvalue reference.But yeah, string literals are lvalue (even though they are `const` cv-qualified), unlike any other literals. – Swift - Friday Pie Aug 30 '23 at 09:36
  • @Swift-FridayPie yes, i totally agree – ofkilmurray Aug 30 '23 at 09:39
  • @WeijunZhou yes, i got that, but... if the reason because a string literal is an lvalue is that it's a const char [DIM] (so an array of char) and it needs to be putted in memory how can be possibole that the sum of 2 string is an rvalue? (i'm not saing that, it's a provocation) – ofkilmurray Aug 30 '23 at 09:42
  • Example, which shows that string literal is an lvalue: you can do `"string2"[3]`. Or `3["string2"]`. Build-in subscript operator requires one of two arguments be a glvalue, and other to be a prvalue of integral type (enum included). – Swift - Friday Pie Aug 30 '23 at 09:44
  • @Swift-FridayPie but if "teststring" is an lvalue how can i bound it as a rvalue reference? ex: void myfunction(std::string &&s) myfunction("ciao"):; – ofkilmurray Aug 30 '23 at 09:48
  • 1
    "rvalue" category (which includes "xvalue") is normally used for expressions referring to objects that will soon be destroyed. String literals exist until the program terminates, so making them xvalues would be confusing. Though I don't think it would break anything important; `&"abc"` would stop working, but it's not something commonly done. – HolyBlackCat Aug 30 '23 at 09:49
  • 1
    *"how can i bound it as a rvalue reference?"* Because you're using the wrong reference type. The literal's actual type is `const char[5]`, and if you used a reference to that type, it [wouldn't compile](https://gcc.godbolt.org/z/xz63Y3EaM). When you're trying to pass it to a function expecting `std::string &&`, a temporary `std::string` is constructed from the literal, and that temporary string is an rvalue. – HolyBlackCat Aug 30 '23 at 09:50
  • @Swift-FridayPie now i have readed your last comment, interesting. I'm thinking about it – ofkilmurray Aug 30 '23 at 09:51
  • 1
    It's not a *name* that makes an lvalue (or a glvalue more generally). It's an *identity*, which approximately means an address. Not a the-compiler-happens-to-temporarily-put-this-in-memory address, but rather a the-language-requires-this-to-have storage-assigned-for-it address. – John Bollinger Aug 30 '23 at 09:52

4 Answers4

2

Character string literals, such as "examplestring", are lvalues because they are basically arrays and they need to be stored in memory.

A string literal is an array of type char [N]. All arrays are non-modifiable lvalues. You can get their address simply by using their name (it decays to a pointer most contexts), but you can't use them on the left side of an assignment:

error: assignment to expression with array type
 "abc" = "def";
       ^

You will get a similar error when attempting this:

int arr1[10] = { ... };
int arr2[10] = { ... };
arr2 = arr1;

Besides the assignment operator, any of the rest of the operators requiring a modifiable lvalue will not work:

"abc"++;
arr1++;
Blagovest Buyukliev
  • 42,498
  • 14
  • 94
  • 130
2

Firstly, we should keep in mind that the reason why string literals are lvalues is that the C++ standard defines them to be lvalues. It is not the case that you can determine whether or not something is an lvalue purely by reasoning about its lifetime, the operations applicable to it, and so on. (And in fact C++ redefined some types of expressions as lvalues that had originally been rvalues in C, such as the result of the prefix increment operator. That does not mean that it had been "wrong" for them to be rvalues in C.)

Now the reason why it is sensible to define string literals to be lvalues and not xvalues is that xvalues are only produced in two ways in C++:

  • when a temporary object is materialized (in this case, the xvalue refers to the just-materialized object, which is indeed expiring soon, like the name "xvalue" suggests), or
  • when an expression is explicitly given type T&& (where T is some object type), i.e., a call to a function whose return type is T&&, or a cast to T&&.

But the array that a string literal refers to is not a temporary object, nor any soon-to-be-expiring object; it is an object with static storage duration, i.e., it will survive until the end of the program.

The built-in & operator can only be applied to lvalues. (As a special rule, a bit-field cannot be the operand of &, even if it's an lvalue.) Does it make sense to give a string literal a value category that allows & to be applied to it, or not? Arguably, it does, because of the permanency of the storage that they refer to.

But you cannot assign to a string literal because it is const. Being const doesn't make something any less of an lvalue.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
1

About value categories of literals

First of all, lvalues are characterized by two properties:

  • they have an identity (such as a name, or an expression that names them)
  • they cannot be moved from (whereas xvalues can be moved from)

It wouldn't make any sense to be able to move from a string literal, since it's an array of of type const char[N]. Being able to move from a const object is meaningless.

Solely based on that, it doesn't make sense for them to be rvalues (i.e. xvalues or prvalues), because these categories imply movability.

It's worth noting that integer literals like 123 are prvalues, so they refer to a new unique object. String literals are different because they refer to an existing object somewhere in memory. It's possible in that in your program, all appearances of "string" refer to the same object, and &"string" == &"string" might be true.

See also Why are string literals l-value while all other literals are r-value?

Passing string literals as rvalue references

Why can I pass a string literal as a rvalue reference?

You cannot.

void take(const char(&&)[1]);

int main() {
    take(""); // error: candidate function not viable: expects an rvalue for 1st argument
}

You are probably confusing a property of the std::string standard library container:

void take(std::string&&);

int main() {
    // OK, creates a new temporary std::string, and through temporary
    // materialization, the rvalue reference std::string&& binds to it.
    take("");
}

More confusion about std::string

std::string a = "string1" + "string2"; if "string1" and "string2" are lvalues, how can their sum can be an rvalue?

You are confusing string literals, which are of type const char[N] with std::string, which is a container from the standard library. Also your code is ill-formed:

// error: invalid operands to binary expression ('const char[8]' and 'const char[8]')
std::string a = "string1" + "string2"

You can perform + between two objects of type std::string because std::string has an overloaded + operator:

auto a = std::string("string1") + std::string("string2");
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • thank you very much. so... if i do: const char string[5] = "ciao"; const char string2[5] = "ciao"; That "ciao" is an lvalue and string1 and string2 refers to the same "ciao"? – ofkilmurray Aug 30 '23 at 10:26
  • @ofkilmurray no, that's a special way of initializating a new array called `string`. `string` is not a string literal, it's just initialized by one. However, it's possible that `&"string" == &"string"` is true because both literals refer to the same object. – Jan Schultke Aug 30 '23 at 10:27
  • ok, thank you, now things are becoming clearer... so... if I ipotetically want to save into a pointer variable the first memory address of "string" how can i do? – ofkilmurray Aug 30 '23 at 10:44
  • 1
    @ofkilmurray with `const char* str = "string";` You can also wrap it in a `std::string_view` and then access the memory address of the string literal with the `.data()` member function. – Jan Schultke Aug 30 '23 at 10:47
1

The "rvalue" category (which includes "xvalue") is normally used for the expressions referring to objects that will soon be destroyed, and can have the resources they own reused.

The string literals exist until the program terminates, so they don't match the "will soon be destroyed" criteria.

Hence making them xvalues would be confusing (but I don't think it would break anything important; just &"abc" wouldn't compile anymore).


Why can I pass a string literal as a rvalue reference?

No you can't:

void a(const char (&)[4]) {}
void b(const char (&&)[4]) {}

int main()
{
    a("abc"); // ok
    b("abc"); // error: cannot bind rvalue reference of type 'const char (&&)[4]' to lvalue of type 'const char [4]'
}

When you're doing this: void a(std::string &&) {} a("abc"); the rvalue reference doesn't point to the literal. It points to a temporary std::string constructed from the literal.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207