I am reviewing C++ references any am trying to reason why the following piece of code complies:
#include <string>
class Foo {
public:
Foo(const std::string& label)
: d_label(label) {}
private:
std::string d_label;
};
int main(int argc, const char** argv) {
Foo("test");
return 0;
}
Here, we are assigning a reference to a const string to a string. In doing so, is a copy of label made that is non-const? If so, why is it that we can make a copy of a const object that is itself non-const? Otherwise, what exactly is going on here, in terms of copy constructor/assignment calls?
In C++ the keyword const actually means read-only. To make a copy of an object, you don't need write access. Therefore, you can copy a const std::string into an std::string.
Also note, that copying in C++ means making a deep copy by default. This is called value semantics. Hence, manipulating the copied string will not do anything to the original string.
Now to your last question: What is going on in the following line?
Foo("test");
The type of "test" is const char[5]. The compiler searches for a matching constructor of Foo. Since "test" is implicitly convertible to std::string via the
basic_string<CharT,Alloc>::basic_string( const CharT * s, const Alloc & a = Alloc() );
constructor, this conversion will be performed, i. e. a temporary std::string is constructed from "test". A const reference to this temporary is then passed to the constructor
Foo::Foo( const std::string & label );
This constructor in turn calls std::strings copy constructor in order to construct the d_label member of Foo:
basic_string<CharT,Alloc>::basic_string( const basic_string & other );
C++ offers both reference semantics and value semantics: Objects have values, and then you can take references to objects.
std::string d_label
d_label is an object holding a string value. It is considered to own the bytes holding the string as a memory resource. This notion of ownership rationalizes using d_label as an interface to modify the string.
const std::string& label
label is a read-only reference to a string. This is not quite the same as "a reference to a const string." It refers to an object that may be (and probably is) not const.
: d_label(label)
This initializes d_label with the content of label using a copy constructor. You may then do what you like with the copy. You know that the process of copying won't modify the underlying object of label because label was declared const &.
Here no assignments take place. d_label(label) initializes the variable d_label with label which subsequently ends up calling copy constructor of string type.
Let's have a closer look:
Foo("test"); initializes const std::string& label with "test" which its type is const char[5]
Here a string reference (label) is getting initialized with a value having type const char[5]. This initialization is valid since "test" through decaying to const char * can be passed to one of string constructors which gets a const char *.
Now label is a reference to a real string object storing "test".
Then, d_label(label) initializes Foo::d_label with the object referred to by label.
At this point copy constructor of string type is called and constructs the Foo::d_label.
Related
I have a code that compiles, and it looks like:
void test1(const std::string &name)................
test1("myName");
But, if I remove the const from the declaration as:
void test2(std::string &name)................
test2("myName");
The code just doesn't compile.
Why the const std::string makes it compile? As far as I understand the same implicit constructor will be called for the char * to be converted to a std::string.
I am using Visual Studio 2013, in case this might be related to the answer.
When the implicit conversion happens, you get a temporary std::string (technically, the important thing is that it is an xvalue). You cannot bind a temporary to a lvalue reference std::string & but you can bind to a const lvalue reference const std::string & or an rvalue reference std::string &&.
The same thing happens here:
const int &x = 5;
int &x = 5; // error
You get an error in second case because a temporary can not bind to a non-const reference.
test("myName"); tries to bind temporary constructed object to a lvalue in second case, hence an error
"myName" is a c-style string and it has the type of const char[]. Since it is not a std::string it needs to be converted to one. When that happens a temporary string is created. That temporary string cannot be bound to a reference but it can be bound to a const reference as it will extend its lifetime to the end of the expression.
You could also pass the string by rvalue reference like void foo(std::string && bar). This will move the temporary string into the function and give you the same effect.
A third option is to pass the string by value void foo(std::string bar). This works with temporaries and non temporaries and will wither make a copy or a move depending on the source of the string.
The problem is with the & you are using for reference. When calling the function with a string literal you cannot pass a non-const reference.
I am trying to push a string in a string vector, like below
void Node::set_val(string &val)
{
this->val.push_back(val);
}
But when I try to call it as below
Obj.set_val("10h;");
I get the below error,
error: no matching function for call to 'Node::set_val(const char [5])'
I assumed that the string in " " is same as string in c++, Why do I get such an error? What has to be changed below?
You are taking in a std::string by non-const reference. Non-const references cannot bind to rvalues, like "10h;", so you can't pass literals in to that function.
If you aren't going to modify the argument, you should take your argument by reference-to-const:
void Node::set_val(const string &val)
// ^^^^^
This way, a temporary std::string will be constructed from your const char[5] and passed in to set_val.
You could improve this by taking in the string by value and moveing it into the vector:
void Node::set_val(string val)
{
this->val.push_back(std::move(val));
}
This prevents you from making some unnecessary copies.
So in C++, const char* is implicitly convertible to std::string because std::string has a (non-explicit) constructor that takes const char*. So what the compiler tries here is to create a temporary std::string object for your function call, like so:
Node.set_val(std::string("10h;"));
However, since you declared the parameter of set_val to be a non-const reference to a std::string, the compiler can't make this conversion work due to the fact that temporary objects can't be bound to non-const references.
There are three ways to make this work, depending on what you want to achieve:
void Node::set_val(const std::string& val) {}
void Node::set_val(std::string val) {}
void Node::set_val(std::string&& val) {}
All will compile (the last one requires C++11 or higher), but seeing your use case, I would recommend to use the second or third one. For an explanation why, try reading a little bit about move semantics in C++11.
The important thing to take away here is that const char* implicitly converts to std::string by creating a temporary object, and temporary objects can't be passed to functions taking non-const references.
You are passing "10h;" which is a const char array.
Fix it by passing a string: Obj.set_val(string("10h")); and edit function to take a string by value:
void Node::set_val(string val) { /* */ }
Or maybe better, edit your function to take a const string&:
void Node::set_val(const string &val) { /* */ }
I read the question posted on
Why does C++ not have a const constructor?
I am still confused why that program can compile. And I tried to offer my opinion on the question, I don't know why it was deleted. So I have to ask the question again.
Here is the program
class Cheater
{
public:
Cheater(int avalue) :
value(avalue),
cheaterPtr(this) //conceptually odd legality in const Cheater ctor
{}
Cheater& getCheaterPtr() const {return *cheaterPtr;}
int value;
private:
Cheater * cheaterPtr;
};
int main()
{
const Cheater cheater(7); //Initialize the value to 7
// cheater.value = 4; //good, illegal
cheater.getCheaterPtr().value = 4; //oops, legal
return 0;
}
And my confusion is :
const Cheater cheater(7)
creates a const object cheater, in its constructor
Cheater(int avalue) :
value(avalue),
cheaterPtr(this) //conceptually odd legality in const Cheater ctor
{}
'this' pointer was used to initialize cheaterPtr.
I think it shouldn't be right. cheater is a const object, whose this pointer should be something like: const Cheater* const this; which means the pointer it self and the object the pointer points to should both const, we can neither change the value of the pointer or modify the object the pointer points to.
but object cheater's cheaterPtr member is something like Cheater* const cheaterPtr. Which means the pointer is const but the object it points to can be nonconst.
As we know, pointer-to-const to pointer-to-nonconst conversion is not allowed:
int i = 0;
const int* ptrToConst = &i;
int * const constPtr = ptrToConst; // illegal. invalid conversion from 'const int*' to 'int*'
How can conversion from pointer-to-const to pointer-to-nonconst be allowed in the initializer list? What really happened?
And here is a discription about "constness" in constructors I tried to offer to the original post:
"Unlike other member functions, constructors may not be declared as const. When we create a const object of a class type, the object does not assume its 'constness' until after the constructor completes the object's initialization. Thus, constructors can write to const objects during their construction."
--C++ Primer (5th Edition) P262 7.1.4 Constructors
If constructors were const, they couldn't construct their object - they couldn't write into its data!
The code you cite as "legal:"
cheater.getCheaterPtr().value = 4; //oops, legal
is not actually legal. While it compiles, its behaviour is undefined, because it modifies a const object through a non-const lvalue. It's exactly the same as this:
const int value = 0;
const int * p = &value;
*const_cast<int*>(p) = 4;
This will also compile, but it's still illegal (has UB).
Your assumptions are incorrect. Taking them one at a time, first code annotation.
class Cheater
{
public:
Cheater(int avalue) :
value(avalue),
cheaterPtr(this) // NOTE: perfectly legal, as *this is non-const
// in the construction context.
{}
// NOTE: method is viable as const. it makes no modifications
// to any members, invokes no non-const member functions, and
// makes no attempt to pass *this as a non-const parameter. the
// code neither knows, nor cares whether `cheaterPtr`points to
// *this or not.
Cheater& getCheaterPtr() const {return *cheaterPtr;}
int value;
private:
Cheater * cheaterPtr; // NOTE: member pointer is non-const.
};
int main()
{
// NOTE: perfectly ok. we're creating a const Cheater object
// which means we cannot fire non-const members or pass it
// by reference or address as a non-const parameter to anything.
const Cheater cheater(7);
// NOTE: completely lega. Invoking a const-method on a const
// object. That it returns a non-const reference is irrelevant
// to the const-ness of the object and member function.
cheater.getCheaterPtr().value = 4;
return 0;
}
You said:
I think it shouldn't be right. cheater is a const object whose this pointer should be something like: const Cheater* const this
cheater is const after construction. It must be non-const during construction. Further, the constructor does not (and cannot) know the caller has indicated the object will be const. All it knows is its time to construct an object, so thats what it does. Furthermore, after construction &cheater is const Cheater *. Making the actual pointer var itself also const is simply non-applicable in this context.
And then...
...object cheater's cheaterPtr member is something like Cheater* const cheaterPtr;
This is actually an incredibly accurate way to describe this. Because cheater is const its members are as well, which means the cheaterPtr member is const; not what it points to. You cannot change the pointer value, but because it is not a pointer-to-const-object you can freely use that pointer to modify what it points to, which in this case happens to be this.
If you wanted both the pointer and its pointed-to-object to be const you whould have declared it as const Cheater *cheaterPtr; in the member list. (and doing that, btw, makes only that mutable action through the getCheaterPointer() invalid. It would have to return a const Cheater* as well, which means of course the assignment would fail.
In short, this code is entirely valid. What you're wanting to see (construction awareness of the callers const-ness) is not part of the language, and indeed it cannot be if you want your constructors to have the latitude to... well, construct.
i just wrote a function:
void doSomeStuffWithTheString(const std::string& value) {
...
std::string v = value;
std::cout << value.c_str();
...
}
but then i call this with
doSomeStuffWithTheString("foo");
and it works. So i would have thought that this to work (a const char* to initialise a implicit instance of std::string) the value would have to be passed by value, but in this case is passed by (const) reference.
Is by any chance a implicit temporal std::string instantiated from const char* when the reference is const? if not, then how this possibly work?
EDIT
what happens if the function is overloaded with
void doSomeStuffWithTheString(const char* value);
which one will choose the compiler?
The std::string type has an implicit conversion (via constructor) from const char*. This is what allows the string literal "foo" to convert to std::string. This results in a temporary value. In C++ it's legal to have a const & to a temporary value and hence this all holds together.
It's possible to replicate this trick using your own custom types in C++.
class Example {
public:
Example(const char* pValue) {}
};
void Method(const Example& e) {
...
}
Method("foo");
Yes, a temporary std::string is constructed from the string literal.
Exactly, using std::string default constructor
"the value would have to be passed by value, but in this case is passed by (const) reference."
There is a C++ feature where it is possible to pass a temporary value (in this case, a temporary std::string implicitly converted from the const char *) to an argument of const-reference (in this case, const std::string &) type.
const std::string s1("foo");
const std::string& s2("foo");
Not sure how they are different but I'm seeing evidence of both usages. Any ideas?
const std::string s1("foo");
This declares a named std::string object as a local variable.
const std::string& s1("foo");
This declares a const reference to a std::string object. An unnamed, temporary std::string object is created with the contents "foo" and the reference is bound to that temporary object. The temporary object will exist until the reference goes out of scope.
In this particular case, there is no observable difference between the two: in both cases you end up with a std::string that can be accessed via the name s1 and which will be destroyed when s1 goes out of scope.
However, in some cases, there is a difference. Consider, for example, a function that returns by reference:
const std::string& get_reference_to_string();
If you initialize s1 with the result of calling this function, there is a difference between:
const std::string s1(get_reference_to_string());
const std::string& s1(get_reference_to_string());
In the first case, a copy of the referenced string is made and used to initialize s1. In the second case, s1 is simply bound to the std::string to which the returned reference refers: no copy is made.
They are identical in meaning, due to the way constant references can bind to temporaries plus string having an implicit conversion from char*.
For clarity and readability, prefer the non-reference.
The first instance creates a constant string variable initialized to hold "foo". The second one creates a constant reference to a string and then initializes that constant reference to point at a temporary string that was initialized to hold "foo".
const std::string s1("foo"); creates a std::string object on the stack and initializes it with the const char* string "foo." const std::string& s1("foo"), on the other hand, is a const reference to an implicitly constructed std::string.