Why string reference can refer to a C-style string?

I don't understand, why a string class reference can refer to a C-style string?
const string &r = "hello";

But the following code cannot work.
1
2
char ok[20] = "I don't understand.";
string &r1 = ok;//where error shows 

only if I declare string as const, then it works:
1
2
char ok[20] = "I don't understand.";
const string &r1 = ok;//it's ok now 
Last edited on
closed account (j3Rz8vqX)
invalid initialization of non-const reference of type 'std::string& {aka std::basic_string<char>&}' from an rvalue of type 'const char*'


You can have a character array set equal to the data.
You can have a string set equal to the data.

You cannot have a string referencing constant data(expression), without being constant; must be done during compile-time.

1
2
char ok[20] = "I don't understand.";
const string &r1 = ok;
It is because a std::string is a class -- a struct that has methods.

One of the methods is an assignment operator that looks (simplified) like this:

std::string& operator = ( const char* s );

Now when you write code like the following it works.

1
2
3
4
5
6
string s;

s = "Hello world";  // s.operator=(const char*) is called 

char ok[] = "Hola mundo";
s = ok;    // s.operator=(const char*) is called  

Hope this helps.
ccdare wrote:
I don't understand, why a string class reference can refer to a C-style string?
const string &r = "hello";

But the following code cannot work.

1
2
char ok[20] = "I don't understand.";
string &r1 = ok;//where error shows  


In both of these snippets of code a temporary is created for the reference to refer to. A temporary cannot bind to a non-const reference, so the second snippet is illegal.

Of course, using the reference in the first snippet after the line where it was created will result in undefined behavior. The temporary it referenced will have been destroyed.
cire,

Of course, using the reference in the first snippet after the line where it was created will result in undefined behavior. The temporary it referenced will have been destroyed.

The question raised my attention since I was learning creating a class constructor, one of whose arguments is type const string &, and I saw that parameters passed to it are all C style strings.
Due to what you said, is it unsafe to set an argument type as const string & to receive C style strings, cause it has a undefined behavior, may destroyed. Should I useconst string get better safety?


Another question,
A temporary cannot bind to a non-const reference, so the second snippet is illegal.
Is this a C++ rule? Is it only for class cases, or to all cases?
Last edited on
Due to what you said, is it unsafe to set an argument type as const string & to receive C style strings, cause it has a undefined behavior, may destroyed. Should I useconst string get better safety?

In Someclass c("literal"); the temporary created will not be destroyed until the full expression is evaluated, which means it will not be destroyed until after the constructor returns. No undefined behavior there.


Is this a C++ rule? Is it only for class cases, or to all cases?

All cases.
> using the reference in the first snippet after the line where it was created will result in undefined behavior.
> The temporary it referenced will have been destroyed.

A reference to an anonymous temporary object extends the lifetime of the anonymous temporary to that of the reference itself (except in a few specific situations).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <cstring>

int counter = 0 ;
std::ostream& ccout() { return std::cout << ++counter << ". " ; }

struct A
{
    A( const char* cstr )
    {
        auto sz = cstr ? std::strlen(cstr) : 0 + 1 ;
        buff = new char[sz]{} ;
        if(cstr) std::strcpy( buff, cstr ) ;
        ccout() << "A{ \"" << buff << "\" } constructed\n" ;
    }

    ~A()
    {
        ccout() << "A{ \"" << buff << "\" } destroyed\n" ;
        std::memset( buff, ' ', std::strlen(buff) ) ;
        delete[] buff ;
    }

    A( const A& ) = delete ;
    A& operator= ( const A& ) = delete ;

    char* buff ;
};

void foo( const A& arg )
{
    ccout() << "foo::arg is alive at this point: " << arg.buff << '\n' ;
}

int main()
{
    char cstr[] = "abcd" ;

    const A& abcd = cstr ; // temporary object A{ "abcd" } constructed

    {
        const A& efgh = "efgh" ; // temporary object A{ "efgh" } constructed
        ccout() << "efgh is alive at this point: " << efgh.buff << '\n' ;

        foo( "ijkl" ) ; // temporary object A{ "ijkl" } constructed
        // end of full expression: anonymous temporary A{ "ijkl" } destroyed

    } // end of lifetime of reference efgh: anonymous temporary A{ "efgh" } destroyed

    const A& mnop = "mnop" ; // temporary object A{ "mnop" } constructed
    ccout() << "mnop is alive at this point: " << mnop.buff << '\n' ;
    ccout() << "abcd is still alive at this point: " << abcd.buff << '\n' ;

} // end of lifetime of reference mnop: anonymous temporary A{ "mnop" } destroyed
  // end of lifetime of reference abcd: anonymous temporary A{ "abcd" } destroyed 

http://coliru.stacked-crooked.com/a/985eb374845cbb84
Thanks a lot!
A temporary cannot bind to a non-const reference

First I thought the reason why the reference must be const is that the temporary object is a const. The thinking comes from that temporary basic types cannot be modified.
Later, I find that temporary objects don't have to be const that aren't const by default. But why the reference referred to it must be const?
Later, I find that temporary objects don't have to be const that aren't const by default. But why the reference referred to it must be const?


Because otherwise you could do stuff like this:

1
2
3
4
5
6
7
8
9
void inc(int& x) {
    ++x;
}

int main() {
   int i = 2;
   inc(i); //fine, i now is 3
   inc(0); //err, 0 is now 1??
}
An rvalue reference (to non-const) can extend the lifetime of an anonymous temporary as a modifiable object.
Just as an lvalue reference to const can extend lifetime of an anonymous temporary as a non-modifiable object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <vector>
#include <string>
#include <memory>

void inc( int&& x )
{
    std::cout << "on entry to inc() -            address: "
              << std::addressof(x) << " value: " << x << '\n' ;
    ++x ;
    std::cout << "just before exiti from inc() - address: "
              << std::addressof(x) << " value: " << x << '\n' ;
}

int main()
{
    int&& ri = 23 ;
    std::string str = "abc" ;
    std::string&& rs = str + "efgh" ;

    std::cout << "address: " << std::addressof(ri) << " value: " << ri << '\n'
              << "address: " << std::addressof(rs) << " value: " << rs << '\n' ;

    ri += 65 ;
    rs += "ijklmnop" ;

    std::cout << "address: " << std::addressof(ri) << " value: " << ri << '\n'
              << "address: " << std::addressof(rs) << " value: " << rs << '\n' ;

    inc(99) ;
}

http://coliru.stacked-crooked.com/a/a04a6ecb36c98476
JLBorges, thanks a lot!
JLBorges, I occationally meet another related question.
lvalue reference to const can extend lifetime of an anonymous temporary as a non-modifiable object
When comes to function return value, it doesn't work.

1
2
3
4
5
6
7
8
const string& returnstring()
{
    return string("badbad");//also tried return "badbad";
}
int main()
{
    cout <<  returnstring();
}

//nothing output
> When comes to function return value, it doesn't work.

It doesn't. It is one of the four "(except in a few specific situations)" that I had mentioned earlier (#3 below):

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression.

...<elided>...

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound ...<elided>... persists for the lifetime of the reference except:

— A temporary bound to a reference member in a constructor’s ctor-initializer persists until the constructor exits.

— A temporary bound to a reference parameter in a function call persists until the completion of the full-expression containing the call.

— The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

— A temporary bound to a reference in a new-initializer persists until the completion of the full-expression containing the new-initializer.
These are the four exceptions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <cstring>

struct A
{
    A( const char* cstr )
    {
        sz = ( cstr ? std::strlen(cstr) : 0 ) + 1 ;
        buff = new char[sz]{} ;
        if(cstr) std::strcpy( buff, cstr ) ;
        std::cout << "A{ \"" << buff << "\" } constructed " ;
        dump() ;
    }

    ~A()
    {
        std::cout << "A{ \"" << buff << "\" } destroyed\n" << std::flush ;
        std::memset( buff, 0, sz ) ;
        delete[] buff ;
    }
    
    void dump() const 
    {  
        std::cout << "{ " ;
        for( std::size_t i = 0 ; i < sz ; ++i ) std::cout << int( buff[i] ) << ' ' ; 
        std::cout << "}\n" ; 
    }

    A( const A& ) = delete ;
    A& operator= ( const A& ) = delete ;

    char* buff ;
    std::size_t sz ;
};

struct S
{
    S( const A& r ) : a(r) {}
    const A& a ;
};

const A& foo( const A& arg ) { return arg ; }

const A& bar() { return A("three") ; }

int main()
{
    // A temporary bound to a reference member in a constructor’s ctor-initializer
    // persists until the constructor exits.
    {
        S one( A("one") ) ;
        // undefined behaviour (access object after it has been destroyed)
        one.a.dump() ; // UB
    }

    // A temporary bound to a reference parameter in a function call
    // persists until the completion of the full-expression containing the call.
    {
        const A& two = foo( A("two") ) ;
        // undefined behaviour (access object after it has been destroyed)
        two.dump() ; // UB
    }

    // The lifetime of a temporary bound to the returned value in a function
    // return statement is not extended; the temporary is destroyed
    // at the end of the full-expression in the return statement.
    {
        const A& three = bar() ;
        // undefined behaviour (access object after it has been destroyed)
        three.dump() ; // UB
    }

    // A temporary bound to a reference in a new-initializer persists until the
    // completion of the full-expression containing the new-initializer.
    {
        struct B { const A& a ; };
        B* pb = new B{ A("four") } ;
        // undefined behaviour (access object after it has been destroyed)
        pb->a.dump() ; // UB
    }
}

http://coliru.stacked-crooked.com/a/fd0481794da86725
Thanks.
Topic archived. No new replies allowed.