How `const std::string& s = nullptr` works as an optional parameter - c++

To my knowledge a reference cannot be null, but when I run code like this:
#include <iostream>
#include <string>
void test(int i, const std::string& s = nullptr) {
std::cout << i << " " << s << std::endl;
}
int main() {
test(1, "test");
test(2);
}
the optional parameter s can be null, and the code is built. What's more, when test(2) runs, the program throws exceptions instead of printing some random strings.
When I changed s to some basic type like int, it failed to compile, so I think the magic stays inside the string class, but how?
And what's more, how can I check if s is null or not? if I using if(s==nullptr) or if(s.empty()), it fails to compile.

test initialized its argument by using constructor number 5 of std::basic_string<char>:
basic_string( const CharT* s,
const Allocator& alloc = Allocator() );
Since it needs to materialize a temporary (std::string) to bind to that reference. That is because a reference must be bound to an object of a correct type, which std::nullptr_t isn't. And said constructor has a not null constraint on the pointer being passed. Calling test without an explicit argument leads to undefined behavior.
To be perfectly clear, there is no such thing as a null reference in a well-formed C++ program. A reference must be bound to a valid object. Trying to initialize one with nullptr will only seek out to do a conversion.
Since a std::string is an object with a well-defined "empty" state, a fixed version can simply pass in a default initialized string:
void test(int i, const std::string& s = {}); // Empty string by default.
Once the contract violation is fixed, s.empty() should give meaningful results again.

Reference indeed can not be null, however const std::string& s = nulltr does not do what you think it does. When second parameter is not specified compiler will create a string object invoking implicit string constructor that takes a pointer to null-terminated string as first parameter. So test(2); invocation looks like this:
test(2, ::std::string(static_cast<char const *>(nullptr), ::std::string::allocator_type()));
Note that passing nullptr as this first parameter causes Undefined Behavior.

Related

reference and literals in C++

I know that "literals" (c strings, int or whatever) are stored somewhere (in a read only data section apparently .rodata) maybe this is not accurate...
I want to understand why this code causes a runtime error:
#include <iostream>
using namespace std;
const int& foo()
{
return 2;
}
const char* bar()
{
return "Hello !";
}
int main() {
cout << foo() << endl; // crash
cout << bar() << endl; // OK
return 0;
}
foo returns a const reference on a literal (2) why does this cause a crash ? is the integer 2 stored in the stack of foo() ?
See also : Why are string literals l-value while all other literals are r-value?
I see why this is confusing so I will try to break it down.
First case:
const int& foo()
{
return 2;
}
The return statement makes a temporary object which is a copy of the literal 2. So its address is either non-extant or different from the location of the literal 2 (assuming literal 2 has a location - not guaranteed).
It is that temporary whose reference is returned.
Second case:
const char* bar()
{
return "Hello !";
}
The return statement makes a temporary object which is a copy of a pointer to the address of the first element of the literal char array. That pointer contains the actual address of the literal array and that address is returned by copy to the caller.
So to sum up. The second one works because the return statement takes a copy of the literal's address and not a copy of the literal itself. It doesn't matter that the storage for the address is temporary because the address still points to the correct place after the temporary holding its value collapses.
That is indeed very confusing, and in order to understand what's happening, one has to dive very deep in the language specification.
But before we do this, let me remind you that compiler warnings are your friends. With a sufficient level of warnings, you should see following when compiling your example:
In function 'const int& foo()': 3 : warning: returning reference to
temporary [-Wreturn-local-addr] return 2; ^
Now, what is happening in your first example? One can not really take an address of the integral literal, since they do not really exist as objects. However, one is allowed to bind constant references to literals. How is it possible, when everybody knows that references are akin to pointers? The reason is that when you bind a const reference to the literal, you do not really bind it to the literal. Instead, compiler creates a temporary variable, and binds your reference to it. And that variable is an object, albeit short-lived one. Once you function returns, the temporary object is destroyed, and you end up with dangling reference -> crash.
In the second example, "hello" is a literal, but you are not returning the literal - you are returning a pointer to the string. And a pointer remains valid, because the string it points to remains valid.

Assigning reference to temp object c++ visual

I noticed in visual studio for c++ whenever I run code like
#include "stdafx.h"
#include <iostream>
int &test() {
int num = 19;
return num;
}
int main() {
auto &num = test();
std::cout << num << std::endl;
}
Which obviously shouldnt work cause num is a local object in test but some how it compiles on visual studio. Also this code compiles
#include <iostream>
#include <string>
int main()
{
std::string str1 = "eyyy";
std::string str2 = "jgasgs";
std::string &c = str1 + str2;
std::cout << c << std::endl;
}
Which shouldnt work cause it assigns a normal reference to a temp object.
#include <iostream>
#include <string>
int main()
{
std::string str1 = "gs";
std::string str2 = "a";
std::string &c = str1 + str2;
std::cout << &(str1 + str2) << std::endl;
}
In here it shouldnt let me do it because str1 + str2 should make a temp and you can't use address of a temp. When I searched it up showshttps://connect.microsoft.com/VisualStudio/feedback/details/807118/temporary-objects-can-be-bound-to-non-const-references which sais put warning 4 on but even when I do it still works so why does this work and how do I fix it? Also in the first example it doesnt work with strings and throws a exception, the second one only works with strings and the third also only works with strings.
If you are using C++ 11 there is a new feature called "Object life extension". And it depends on the context.
http://en.cppreference.com/w/cpp/language/lifetime
"Lifetime of a temporary
Whenever a reference is bound to a temporary or to a base subobject of a temporary, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:
a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists. (note: such initialization is ill-formed as of DR 1696)
(until C++14)
a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference.
a temporary bound to a reference in the initializer used in a new-expression exists until the end of the full expression containing that new-expression, not as long as the initialized object. If the initialized object outlives the full expression, its reference member becomes a dangling reference.
In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime."
In C++, "shouldn't work" and "doesn't compile" are distinct things.
In particular, warnings emitted by the compiler are just that; they don't prevent compilation. Compilers usually have an option called "treat warnings as errors" that you should enable whenever possible.
Hopefully, this also catches the fact that you are returning a reference to a local variable, which is another thing that shouldn't work and yet will happily compile (and even appear to run correctly in simple cases).

STL Push_back string in vector

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) { /* */ }

Why it does not work when I pass reference to this function?

The code below:
void test(string &s){ // if the argument is "string s", it works
return test(s+',');
}
The compiler reports cannot find the function: test(std::basic_string).
I think the compiler would create a temporary string (== s+','), and I can pass its reference.
But it seems I am wrong. I do not know why I cannot pass the reference of this temporary string.
You can't bind a temporary to a non-constant reference. You could either take the argument by const reference (or, as you point out, by value)
void test(string const & s){ // or string s
return test(s+',');
}
or use a named variable rather than a temporary
void test(string & s){
std::string s2 = s + ',';
return test(s2);
}
As noted, at great length, in the comments, this code has undefined runtime behaviour and shouldn't be used in "real" code; it's purpose is just a minimal example of how to fix the observed compilation error
make it const:
void test(const std::string &s){ // if the argument is "string s", it works
return test(s+',');
}
But first you should have seen this question String Concatenation
concatenating strings using "+" in c++
Alternative Solution
void test(string &s)
{
s.append(",");
return test(s);
}
Standard C++ does not allow a non-const lvalue reference to be bound to an rvalue. In your example, the result of the expression s+',' is a temporary and thus an rvalue, so the compiler is required to discard the overload of test expecting an lvalue reference, leaving it no overload available to call. That's why it complains about not being able to find the function test.
To solve this issue you have to provide an overload whose parameter may be bound to an rvalue. As you realized yourself, expecting an argument by-copy works, but it may imply unnecessary overhead by calling copy/move-ctors. A better option would be to expect the argument by reference. Since const lvalue references may be bound to rvalues, declaring the function as follows solves the problem.
void test(std::string const& s)

Question about exact time of destruction of temporaries in C++

is the following code safe (it works in DEBUG) :
void takesPointer(const Type* v);//this function does read from v, it doesn't alter v in any way
Type getValue();
...
takesPointer(&getValue());//gives warning while compiling "not an lvalue"
...
Type tmp = getValue();
takesPointer(&tmp);//this is safe, and maybe I should just do it, instead of posting here
so - is it safe ? should I just forget about it and use the code with the explicit tmp ?
but anyways - I'm still interested if the optimizer is allowed to kill the temporary before returning from this call :
takePointer(&getValue())
EDIT:
thank you all !
unfortunately I can't change the function "takesPointer" (it's part of a library), I could only wrap it in a function "takesReference", which calls takesPointer - would this eliminate the copy, or would the compiler still be allowed to create a copy (the "Type" is a int-3x3-Matrix, so it wouldn't be THAT bad, but still...) ?
inline void takesReference(const Type& v){ takesPointer(&v); }
About the time of destruction : will it be destroyed after "takesPointer" RETURNS, or after it's CALLED ?
As other answers have stated you cannot take the address of a temporary. However, if you change the signature of
void takesPointer(const Type* v);
to
void takesPointer(const Type& v);
then the following code should compile without warnings:
takesPointer(getValue());
because you are allowed to bind a temporary to a const reference, and it should work just the same.
The Standard forbids you to do &getValue() - exactly because it's not an lvalue. Ordinarily, if this was allowed, then the temporary resulting from that function call will live until the outer function returns and every other thing in the whole expression has completed being processed. This can be called "destroying temporaries after end of full-expression", and ensures things like the following works as expected
// the temporary string is alive until the whole expression has been processed
cout << string("hello");
The compiler gives you a diagnostic - that's all the Standard requires for ill-formed code. It doesn't force the compiler to abort compilation, for instance. But after code being ill-formed was diagnosed, the compiler can do everything it wants. So if you want to know what the compiler does in your case, you should read its manual.
This will prevent copies* and compiles.
const Type& tmp = getValue();
takesPointer(&tmp);
Prevent copies is a bit strong because the compiler will often do this for you.
You have to have access to the copy constructor but the compiler will often not use it:
#include "iostream"
class Type
{
public:
explicit Type(int val) : m_val(val) { std::cout << "ctor";};
Type(const Type& copy)
{
std::cout << "copy";
};
private:
int m_val;
};
Type getValue() { Type r(0); return r;};
void takesPointer(const Type* const)
{
};
int main(int argc, char* argv[])
{
const Type tmp = getValue();
takesPointer(&tmp);
}
Will print only "ctor" in release and "ctorcopy" in debug. (MVS2005)
You are allowed to bind a non-const rvalue to a const reference lvalue, but you're binding it to a const pointer lvalue.
And no, the optimzier can't destruct the result of getValue() before calling takePointer().
Yes, it is safe, albeit illegal in the current form. You can work around the error by using an explicit intermediate cast to const-reference type
takesPointer( &(const Type &) getValue() );
This makes it perfectly legal as long as the temporary object is alive, which is till the end of evaluation of the full expression.
Moreover, you can even cast away the constness and modify the temporary object through the pointer (keeping in mind that it will be destroyed at the end of full expression). This is perfectly legal as long as the temporary object itself is not constant.
Using a comma operator, you can "stretch" full expressions, and thus write quite extensive sequences of operations that work with a "long-lived" temporary object
Type *p;
p = &(Type &) (const Type &) getValue(), modify(p), print(p), modifyAgain(p), print(p);
// Not using C++ casts for brevity
The practice is rather questionable though and most of the time there's no point in doing this.