11

Why can a const reference to a string parameter take string literals? String literals, like "hello", are not variables, so why is this code valid?

class CVector {
    public:
        int x, y;
        CVector() {};
        ~CVector() { delete ptr; }
        string* ptr;
        void doSomething(const string& str) { ptr = new string(str); }
        void print() { cout << "\n" << *ptr; }
};
int main()
{
    result.doSomething("asdas");
    result.print();
    return 0;
}

First of all, I thought that references as parameters were used to avoid the copying process and directly access the variable taken as argument(I could still be correct though). But the string literal "asdas" is not a variable, so why can the parameter take string literals as argument? I mean since the parameter str is a reference, it will become an alias for that entity, right? If so, did the literal just become a variable?

Shouldn't the parameter list consist of string& str instead of the const reference, so that the literal would be used in the construction of str?

And doesn't a const reference keep the referenced entity alive for as long as the reference is alive? If so, why would you do that to a literal?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • simply put, there is a conversion(i.e. ctor) that takes string literal and creates string instance. – Kobi May 22 '17 at 19:13

3 Answers3

11

When you do

result.doSomething("asdas");

The compiler looks to see if you have a doSomething(const char[]); and finds nothing. Since there is not suitable function it then tries to find an overload that takes something that can be constructed from a const char[] and it finds doSomething(const string& str). Since the compiler is allowed to make one user defined conversion it constructs a temporary std::string from the string literal and pass that temporary, via reference to const, to the function.

Shouldn't the parameter list consist of string& str instead of the const reference, so that the literal would be used in the construction of str?

No, this only works with a reference to const and will not work with a regular reference as regular references cannot bind to temporaries.

And doesn't a const reference keep the referenced entity alive for as long as the reference is alive? If so, why would you do that to a literal?

The reference to const will extend the lifetime of the object only when it is a function local object. Inside the scope of the function the object will be alive as the expression that called that function has not ended but if you were to try and keep a reference to the std::string in the class that would not work.

Effectively you code is translated into

int main()
{
    CVector result
    {
        std::string temp = "asdas";
        result.doSomething(temp);
    }
    result.print();
    return 0;
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Note that the object being passed into the function is the temporary `string` that was constructed, not the literal itself. – Mark Ransom May 22 '17 at 19:22
  • `result.result.doSomething("asdas");("asdas");` doesn't look right – Lightness Races in Orbit May 22 '17 at 19:31
  • Thanks. But so it the parameter const string& str) a reference the the literal "asdas" itself or another string created from that literal? –  May 23 '17 at 17:49
  • @JakeBlandon the parameter is a reference to a temporary string instance that is created from the string literal. You are not actually referring to the string literal. – NathanOliver May 23 '17 at 17:55
  • So the compiler actually creates a string every time you are writing a string literal? Like in this case, where the compiler creates a string from the string literal and then make the parameter refer to that string. –  May 23 '17 at 18:09
  • @JakeBlandon No. It only creates a temporary here because that is the only way to allow you to call the function. The compiler is allowed to create one temporary object to try and make the code compile. – NathanOliver May 23 '17 at 18:10
  • Ahh okay, thank you! But is this temporary an object that has lifetime in the scope it was creates(in this case, scope of function main) or doesn't it have a scope? Like, is it directly created by the compiler? @NathanOliver –  May 23 '17 at 18:33
  • 1
    @JakeBlandon It lives for until the end of the expression. That is to say that it will be destroyed after `doSomething` returns and before the call to `print` – NathanOliver May 23 '17 at 18:39
  • Makes sense. But can you even say anything about the scope of these temporaries or don't they have one? Finally, is there somewhere I can read more about when and how the compiler choose to make an actual from a literal? –  May 23 '17 at 19:04
  • 1
    @JakeBlandon They are scoped to the full expression that created them. When the expression ends the temporary is destroyed. Generally a full expression is something that ends with a `;`. As far as further resources I don't have any except for the books/references from here: http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – NathanOliver May 23 '17 at 19:10
4

String literals, like "hello", are not variables

The term "variables" is pretty vaguely defined, and not really backed up by any concrete concepts.

The expression "hello" represents an object with static storage duration that you cannot modify. Like any other expression, it may potentially be used to initialise some other object. In this case, you're initialising a std::string with the expression (after its decay to a const char*).

What you're missing is the "middle step", the construction of a temporary std::string from that literal, whose lifetime is then extended via the binding to a ref-to-const.

So, ish:

const std::string temp{"Hello world"};   // the compiler creates this transparently
const std::string& ref = temp;  // this is yours, and it extends temp's life

Read about implicit conversions for more information.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1

std::string has an implicit const char * converting constructor.

The compiler is allowed to do one implicit conversion to make types match, so it uses said ctor to convert the const char * to a std::string temporary and it's smooth sailing from there since const& (const lvalue references) are allowed to bind to temporaries (and extend their lifetime).

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70