This question already has an answer here:
What is the lifetime of reference member default initializer in C++?
(1 answer)
Closed 1 year ago.
I have a question about const std::function& as a member variable.
In the below code, I was expecting to see "2" printed out. But on my compiler (gcc c++17), I saw "1", Why?
using Fn = std::function<bool()>;
class Foo
{
public:
Foo () {}
void bar(){
if(fn_)
std::cout<<"1"<<std::endl;
else
std::cout<<"2"<<std::endl;
}
private:
const Fn& fn_ = nullptr;
};
int main()
{
Foo f;
f.bar();
}
I think what's happening here is that your line
const Fn& fn_ = nullptr;
is essentially doing
const Fn& fn_ = Fn(nullptr);
So that reference is dangling and you're invoking undefined behaviour by accessing it.
If you do it by value instead of by reference, or if you pass in a valid empty Fn object (with sufficient lifetime) to the constructor to bind the ref member to, the output is what you expect.
Related
This question already has answers here:
What's a use case for overloading member functions on reference qualifiers?
(4 answers)
Closed 10 months ago.
I just discovered this is valid C++:
struct S {
int f() &; // !
int g() const &; // !!
};
int main() {
S s;
s.f();
s.g();
}
IIUC, this is passed to f by reference and to g be passed by const-reference.
How was this useful to anyone?
They are useful for both providing safety and optimizations.
For member functions returning a pointer to something they own (either directly or via view types like std::string_view or std::span), disabling the rvalue overloads can prevent errors:
struct foo {
int* take_ptr_bad() { return &x; }
int* take_ptr() & { return &x; }
int x;
};
foo get_foo();
void bar() {
auto ptr = get_foo().take_ptr_bad();
// ptr is dangling
auto ptr = get_foo().take_ptr();
// does not compile
}
The other is to provide some optimizations. For instance, you might overload a getter function to return an rvalue reference if this is an rvalue to prevent unnecessary copies:
struct foo {
const std::string& get_str() const & {
return s;
}
std::string&& get_str() && {
return std::move(s);
}
std::string s;
};
void sink(std::string);
foo get_foo();
void bar() {
sink(get_foo().get_str());
// moves the string only if the r-value overload is provided.
// otherwise a copy has to be made, even though the foo object
// and transitively the string is temporary.
}
These are how I use the feature, and I'm sure there are more use cases.
What would be a valid use case for a signature like this?:
T&& foo();
Or is the rvalue ref only intended for use as argument?
How would one use a function like this?
T&& t = foo(); // is this a thing? And when would t get destructed?
For a free function it doesn't make much sense to return a rvalue reference. If it is a non-static local object then you never want to return a reference or pointer to it because it will be destroyed after the function returns. It can possibly make sense to return a rvalue reference to an object that you passed to the function though. It really depends on the use case for if it makes sense or not.
One thing that can greatly benefit from returning an rvalue reference is a member function of a temporary object. Lets say you have
class foo
{
std::vector<int> bar;
public:
foo(int n) : bar(n) {}
std::vector<int>& get_vec() { return bar; }
};
If you do
auto vec = foo(10).get_vec();
you have to copy because get_vec returns an lvalue. If you instead use
class foo
{
std::vector<int> bar;
public:
foo(int n) : bar(n) {}
std::vector<int>& get_vec() & { return bar; }
std::vector<int>&& get_vec() && { return std::move(bar); }
};
Then vec would be able to move the vector returned by get_vec and you save yourself an expensive copy operation.
T&& t = foo(); // is this a thing? And when would t get destructed?
An rvalue reference is really similar to a lvalue reference. Think about your example like it was normal references:
T& foo();
T& t = foo(); // when is t destroyed?
The answer is that t is still valid to use as long as the object is refers to lives.
The same answer still applies to you rvalue reference example.
But... does it make sense to return an rvalue reference?
Sometimes, yes. But very rarely.
consider this:
std::vector<int> v = ...;
// type is std::tuple<std::vector<int>&&>
auto parameters = std::forward_as_tuple(std::move(v));
// fwd is a rvalue reference since std::get returns one.
// fwd is valid as long as v is.
decltype(auto) fwd = std::get<0>(std::move(parameters));
// useful for calling function in generic context without copying
consume(std::get<0>(std::move(parameters)));
So yes there are example. Here, another interesting one:
struct wrapper {
auto operator*() & -> Heavy& {
return heavy;
}
auto operator*() && -> Heavy&& {
return std::move(heavy);
}
private:
Heavy instance;
};
// by value
void use_heavy(Heavy);
// since the wrapper is a temporary, the
// Heavy contained will be a temporary too.
use_heavy(*make_wrapper());
I think a use case would be to explicitly give permission to "empty" some non-local variable. Perhaps something like this:
class Logger
{
public:
void log(const char* msg){
logs.append(msg);
}
std::vector<std::string>&& dumpLogs(){
return std::move(logs);
}
private:
std::vector<std::string> logs;
};
But I admit I made this up now, I never actually used it and it also can be done like this:
std::vector<std::string> dumpLogs(){
auto dumped_logs = logs;
return dumped_logs;
}
This question already has answers here:
What is "rvalue reference for *this"?
(3 answers)
Closed 4 years ago.
By mistake, I had a & at the end of a prototype (see example below). Neither gcc nor Clang complains about it. I noticed that the symbol generated is not exactly the same.
Example:
class A
{
public:
void fn() &
{
return;
}
};
int main()
{
A a;
a.fn();
return 1;
}
Name of the symbol without &: _ZN1A2fnEv and with &: _ZNR1A2fnEv.
What does it mean ? Do I miss something ?
Thanks,
The & at the end of the member function declaration is a ref qualifier. It applies to the object value on which the member function is called, and constrains that value's value category:
Functions without ref qualifiers can be called on any value.
Functions with & qualifier can only be called on lvalues.
Functions with && qualifier can only be called on rvalues.
The ref qualifier affects overload resolution, e.g. an overload on a mismatched instance value is not viable.
The standard library doesn't use ref qualifiers much (I can only think of std::optional), so let's make up our own example:
struct X {
explicit X(int n) : s_(n, 'a') {}
std::string s_;
const char* f1() const { return s_.c_str(); }
const char* f2() const & { return s_.c_str(); }
const char* f3() const && { return s_.c_str(); }
};
Now consider the following calls:
int main() {
X x(10);
x.f1(); // OK
X(20).f1(); // OK
x.f2(); // OK
X(20).f2(); // ill-formed
x.f3(); // ill-formed
X(20).f3(); // OK
}
The example also demonstrates why this feature may be useful: When a member function returns a reference to some internal part of the object itself, then it is important that that internal reference does not outlast the lifetime of the object. If your member function is unqualified, then you can very easily introduce lifetime bugs. For example:
const char* s = std::string("abcde").c_str(); // dangling pointer!
One way to improve such "internal state access" APIs is to create different return value (categories) for different ref-qualified overloads. To get back to std::optional, the engaged-access essentially boils down to this set of overloads:
struct MyOptional {
T value_; // assume engaged!
T& get() & { return value_; }
T&& get() && { return std::move(value_); }
};
That means that MyOptional::get returns an lvalue when invoked on an lvalue optional, and an rvalue (in fact an xvalue) when invoked on an rvalue. This means that, given MyOptional x;, the binding T& r = x.get(); is allowed, but T& r = MyOptional().get(); is not, and similarly T&& r = x.get(); is disallowed, but T&& r = std::move(x).get() is allowed.
According to §7.1.5.1/4:
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.
So my question becomes: when is an object a const object?
In particular, is a const member in a non-const object considered a const object?
class Foo {
const Bar bar;
void replaceBar(Bar bar2) {
*(const_cast<Bar *>&bar) = bar2; // Undefined behavior?
}
}
This comes up because I have an immutable class (all fields are const), but I want to have a move constructor, which technically modifies the value passed in. I'm ok with "cheating" in that case, since it doesn't break logical constness.
The simple rule is: it is ok to cast away constness if the original object is not const. So if you have a non-cont object and, say, you pass the const reference to it to a function, it is legal to cast away constness in the function.
In your example the original object is const, so casting constness away is undefined behaviour.
Let us make this a full example:
struct Bar { int x; };
struct Foo {
const Bar bar;
Foo( int x ):bar(x) {}
void replaceBar(Bar bar2) {
*(const_cast<Bar *>&bar) = bar2; // Undefined behavior?
}
};
now, let us break the world.
int main() {
Foo f(3);
Bar b = {2};
f.replaceBar(b);
std::cout << f.bar.x << "\n";
}
the above can and probably should output 3, because a const object Bar was created with x=3. The compiler can, and should, assume that the const object will be unchanged throughout its lifetime.
Let's break the world more:
struct Bar {
int* x;
Bar(int * p):x(p) {}
~Bar(){ if (x) delete x; }
Bar(Bar&& o):x(o.x){o.x=nullptr;}
Bar& operator=(Bar&& o){
if (x) delete x;
x = o.x;
o.x = nullptr;
}
Bar(Bar const&)=delete;
Bar& operator=(Bar const&)=delete;
};
struct Foo {
const Bar bar;
Foo( int* x ):bar(x) {}
void replaceBar(Bar bar2) {
*(const_cast<Bar *>&bar) = bar2; // Undefined behavior?
}
};
now the same game can result in the compiler deleting something twice.
int main() {
int* p1 = new int(3);
Foo f( p1 );
Bar b( new int(2) );
f.replaceBar(std::move(b));
}
and the compiler will delete p1 once within replaceBar, and should delete it also at the end of main. It can do this, because you guaranteed that f.bar.x would remain unchanged (const) until the end of its scope, then you violated that promise in replaceBar.
Now, this is just things the compiler has reason to do: the compiler can literally do anything once you have modified an object that was declared const, as you have invoked undefined behavior. Nasal demons, time travel -- anything is up for grabs.
Compilers use the fact that some behavior is undefined (aka, not allowed) to optimize.
Please consider the following code.
struct foo
{
};
template<typename T>
class test
{
public:
test() {}
const T& value() const
{
return f;
}
private:
T f;
};
int main()
{
const test<foo*> t;
foo* f = t.value();
return 0;
}
t is a const variable and value() is a constant member-function which returns const T&. AFAIK, a const type is not assignable to a non-const type. But how foo* f = t.value(); compiles well. How this is happening and how can I ensure value() can be only assigned to const foo*?
Edit
I found that, this is happening on when templates are used. Following code works as expected.
class test
{
public:
test() {}
const foo* value() const { return f; }
private:
foo* f;
};
int main()
{
const test t;
foo* f = t.value(); // error here
return 0;
}
Why the problem is happening when templates are used?
Because you have two levels of indirection - in your main function, that call to value returns a reference to a const pointer to a non-const foo.
This can safely be copied into non-const pointer to a non-const foo.
If you'd instantiated test with const foo *, it would be a different story.
const test<const foo*> t;
foo* f = t.value(); // error
const foo* f = t.value(); // fine
return 0;
Update
From the comment:
value() returns const T& which can
only be assigned to another const
type. But in this case, compiler is
safely allowing the conversion.
Const data can only be read. It cannot be written ("mutated"). But copying some data is a way of reading it, so it's okay. For example:
const int c = 5;
int n = c;
Here, I had some const data in c, and I copied the data into a non-const variable n. That's fine, it's just reading the data. The value in c has not been modified.
Now, suppose your foo had some data in it:
struct foo { int n; };
If I have a non-const pointer to one of those, I can modify the n value through the pointer. You asked your test template to store a pointer to a non-const foo, and then made a const instance of test. Only the pointer address is constant, therefore. No one can change the address stored in the pointer inside test, so it cannot be made to point to another object. However, the object it points to can have its contents modified.
Update 2:
When you made your non-template version of the example, you made a mistake. To get it right, you need to substitute foo * into each place where there's a T.
const T& value() const
Notice that you have a reference to a const T there. So the return value will be a reference to something const: a foo *. It's only the pointer address that can't be modified. The object it points to can have its contents modified.
In your second example, you got rid of the reference part, which changes the meaning and makes the const modifier apply to the object that the pointer points to, instead of applying to the pointer itself.
Use the following template specialization:
template<typename T>
class test<T*>
{
public:
test() {}
const T* value() const
{
return f;
}
private:
T* f;
};
After including this, g++ says:
d.cpp: In function ‘int main()’:
d.cpp:41: error: invalid conversion from ‘const foo*’ to ‘foo*’
There's nothing wrong in your code, having a const reference to a pointer only means that you can't modify the pointer, but the pointed-to object remains perfectly mutable. If inside your main function you try to change the address pointed to by the f member of t you'll see that you can't: encapsulation is perfectly preserved.
This is the same principle that makes the following code valid:
void foo(std::vector<int *> const & v)
{
*v[0] = 0; // op. [] returns const & to int *
}
People new to C++ are usually surprised by this behavior, because for them a const vector should not allow the modification of its elements. And in fact it doesn't, because the pointer stored in the vector does not change (it keeps pointing to the same address). It's the pointed-to object which is modified, but the vector does not care about that.
The only solution is to do as Amit says and provide a specialization of your class for T*.