Const and reference member function qualifiers - c++

Let's say we have member class with two member functions defined as follows:
class SomeClass
{
private:
int val = {};
public:
const int getVarLRef() & {
return val;
}
const int getVarCLRef() const& {
return val;
}
};
int main()
{
auto var1 = SomeClass().getVarCLRef();
auto var2 = SomeClass().getVarLRef();
return 0;
}
I not quite understand what is the difference between const& and &.
Why it works with getVarCLRef if we specified this function as const&? Shouldn't it be allowed to be invoked only with lvalues?
getVarLRef, on the other hand, works just fine and fails to compile in this case as expected.
I use C++11 and gcc 7.3.0

Const and reference member function qualifiers are to be able to apply those qualifier to "this" as for regular parameter, so mainly, you have something like:
int getVarLRef(SomeClass& self) { return self.val; }
int getVarCLRef(const SomeClass& self) { return self.val; }
And there, I think you know that:
getVarCLRef(SomeClass()); // Valid, temporary can bind to const lvalue reference
getVarLRef(SomeClass()); // INVALID, temporary CANNOT bind to non-const lvalue reference

Shouldn't it be allowed to be invoked only with lvalues?
Because rvalue could be bound to lvalue-reference to const too. Just as the following code works.
const SomeClass& r = SomeClass();
On the other hand, rvalue can't be bound to lvalue-reference to non-const, then the invocation of getVarLRef fails as you expected.

Related

why would returned value of "make_share<Type>" in function list be rvalue?

here is my code:
#include <bits/stdc++.h>
class Quote {
public:
Quote() = default;
Quote(const string& book, double sales_price)
: bookNo(book), price(sales_price) {}
string isbn() const { return bookNo; }
private:
string bookNo;
double price = 0.0;
};
class Basket {
public:
void add_item(shared_ptr<Quote>& sale) { items.insert(sale); }
double total_receipt(ostream& s) const;
private:
static bool compare(const shared_ptr<Quote>& lhs,
const shared_ptr<Quote>& rhs) {
return lhs->isbn() < rhs->isbn();
}
multiset<shared_ptr<Quote>, decltype(compare)*> items{compare};
};
int main() {
Basket item;
item.add_item(make_shared<Quote>("aaaa", 1)); //here is the problem is
return 0;
}
when i complier it.I got wrong message cannot bind non-const lvalue reference of type ‘std::shared_ptr&’ to an rvalue of type ‘std::shared_ptr’
Then i change code to this:
#include <bits/stdc++.h>
class Quote {
public:
Quote() = default;
Quote(const string& book, double sales_price)
: bookNo(book), price(sales_price) {}
string isbn() const { return bookNo; }
private:
string bookNo;
double price = 0.0;
};
class Basket {
public:
void add_item(const shared_ptr<Quote>& sale) { items.insert(sale); } //after debug i add "const" at the front of ''shared_ptr<Quote>& sale'',it works
double total_receipt(ostream& s) const;
private:
static bool compare(const shared_ptr<Quote>& lhs,
const shared_ptr<Quote>& rhs) {
return lhs->isbn() < rhs->isbn();
}
multiset<shared_ptr<Quote>, decltype(compare)*> items{compare};
};
int main() {
Basket item;
item.add_item(make_shared<Quote>("aaaa", 1)); //here is the problem is
return 0;
}
It works.So that means make_shared<Quote>("aaaa",1)returns a rvalue type?
But i change function main() to this:
int main() {
Basket item;
shared_ptr<Quote> ptr = make_shared<Quote>("aaaa", 1);
item.add_item(ptr);
cout << endl;
}
and delete const at the front of shared_ptr<Quote>& salein function list void add_item( const shared_ptr<Quote>& sale) { items.insert(sale); }.It also woks.So that means make_shared<Quote>("aaaa",1) returns a lvalue type.It conflicts to mentioned before.
So can i think that when make_shared<Quote>("aaaa",1)in a function list ,it returns rvalue?
I also wanna know why would be this.Thanks!
std::make_shared returns a std::shared_ptr<...>, which is not a reference type, therefore the expression std::make_shared<...>(...) is a prvalue (a subcategory of rvalues). This is true for all functions returning objects by-value, rather than by-reference.
Non-const lvalue references cannot bind to rvalues, as the message is telling you, while const lvalue references and rvalue references can.
The line
shared_ptr<Quote> ptr = make_shared<Quote>("aaaa", 1);
works, because there is a constructor of shared_ptr<Quote> which accepts rvalues, namely the move constructor. Since C++17 it is even simpler, since it is guaranteed that initializing a variable with a prvalue of the same type doesn't move or copy. Instead it will directly construct the return value of std::make_shared in ptr.
In the line
item.add_item(ptr);
the expression ptr is a lvalue. The names of variables used as expressions are always lvalues. And because the type of ptr is not const-qualified, a non-const lvalue reference can bind to it.
returns a rvalue type
rvalue and lvalue are value categories. These are not properties of types, they are properties of expressions. I think you might be confusing them with rvalue references and lvalue references, which are qualities of types.
So that means make_shared("aaaa",1) returns a lvalue type.It conflicts to mentioned before.
No, it doesn't mean that make_shared<Quote>("aaaa",1) returns an lvalue type.
From std::make_shared:
std::make_share returns an expression whose value category is an rvalue.
The reason your last case works is explained below.
When you wrote:
shared_ptr<Quote> ptr = make_shared<Quote>("aaaa", 1); //this is initialization and uses one of shared_ptr's constructor
The above statement means 2 things:
ptr is an object of type shared_ptr<Quote>
The value category of expression ptr is lvalue.
Next, you wrote:
item.add_item(ptr); //this works because the expression ptr is an lvalue
And since we can bind a non-const lvalue reference to an lvalue there is no error in this case even if you remove the const from add_item's parameter.

const_cast: modifying a formerly const value is only undefined if the original variable is const

https://stackoverflow.com/a/332086/462608
modifying a formerly const value is only undefined if the original variable is const
...
if you use it to take the const off a reference to something that wasn't declared with const, it is safe.
...
This can be useful when overloading member functions based on const, for instance. It can also be used to add const to an object, such as to call a member function overload.
I am unable to understand the meanings of the above quotes. I request you to give me examples to practically show what these quotes mean.
Regarding your first two quotes:
void do_not_do_this(const int& cref) {
const_cast<int&>(cref) = 42;
}
int main() {
int a = 0;
// "if you use it to take the const off a reference
// to something that wasn't declared with const, it is safe."
do_not_do_this(a); // well-defined
// a is now 42.
// "modifying a formerly const value is only
// undefined if the original variable is const"
const int b = 0;
do_not_do_this(a); // undefined behavoiur
}
Regarding your final quote:
// "This can be useful when overloading member functions based
// on const, for instance. It can also be used to add const
// to an object, such as to call a member function overload."
class A {
const int& get() const
{
// ... some common logic for const and
// non-const overloads.
return a_;
}
int& get() {
// Since this get() overload is non-const, the object itself
// is non-const in this scope. Moreover, the a_ member
// is non-const, and thus casting away the const of the return
// from the const get() (after 'this' has been casted to
// const) is safe.
A const * const c_this = this;
return const_cast<int&>(c_this->get());
}
private:
int a_{0};
}
How about this:
#include <iostream>
void foo(const int& ub, const int& ok)
{
const_cast<int&>(ub) = 0.0; // undefined behaviour - the original object is const
const_cast<int&>(ok) = 1.0; // this is fine - the original object is not const
}
int main()
{
const int ub = 1.0;
int ok = 0.0;
foo(ub, ok);
std::cout << ub << " " << ok << std::ends;
}
Note the output on common compilers is 1 1: the rationale being that the compiler knows that ub cannot change in main so it substitutes 1 for ub in the std::cout call.
Your third paragraph is alluding to a function body of a non-const member function calling the const version as a means of obviating code repetition.

What's the meaning of a prototype ending with a & (ampersand) [duplicate]

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.

Why can C++ const references be collasped into non-const references

Consider the following C++ program:
#include <iostream>
template<typename T>
class A
{
public:
explicit A(T& x) : x_(x){}
const T& get() { return x_; }
private:
T x_;
};
int main()
{
int x = 42;
A<int&>(x).get() = 43; // compiles fine, even though get() looks like it returns a const ref
std::cout << x << '\n';
}
The program compiles OK and outputs 43. This suggests that the seemingly const reference returned by get() is in fact a non-const reference, because it allows to modifies the value it refers to.
Is it a rule of reference collapsing that causes this behaviour?
How to enforce that the reference returned from get() behaves like a const reference, that is, it doesn't allow to modify the value it refers to?
Is it a rule of reference collapsing that causes this behaviour?
Yes. You have:
T = int&
const T& = const (int&) &
References can't be const (you can't rebind them anyways, so it's ignored) and a reference to a reference is just a reference.
So you have
const T& = int&
To fix this, you need to apply const to the underlying type, which you can do like this by removing the reference:
const std::remove_reference_t<T>& get() { return x_; }
// ^^^^^^^^^^^^^^^^^^^^^^^

Access to reference in member variable discards constness

I made a wrapper around an object in my code that should modify accesses to the object. I choose to use an object here for testing instead of a functor that would have the same functionality. Basically: The wrapper receives a reference to the object and forwards all indexed accesses to the object (after some possible manipulation)
Now comes the problem: The accessor discards constness of the wrapped object.
Minimal Example
struct Foo
{
std::array<int, 2> data;
const int& operator()(int idx) const{
return data[idx];
}
int& operator()(int idx){
return data[idx];
}
};
struct Bar
{
Foo& ref;
Bar(Foo& r):ref(r){}
int& operator()(int idx) const{
return ref(idx);
}
};
template< typename T >
void test(const T& data){
data(1) = 4;
std::cout << data(1);
}
void main(){
Foo f;
test(f);
// Above call does not compile (as expected)
// (assignment of read-only location)
Bar b(f);
test(b); // This does compile and works (data is modified)
}
Declaring the ()-operator of Bar (the wrapper) "const", I'd expect to be all member accesses "const" to. So it shouldn't be possible to return an "int&" but only a "const int&"
However gcc4.7 happily compiles the code and the const is ignored. Is this the correct behavior? Where is this specified?
Edit:
On a related issue: If use typedefs in Foo like:
struct Foo
{
using Ref = int&;
using ConstRef = const int&; //1
using ConstRef = const Ref; //2
int* data; // Use int* to have same issue as with refs
ConstRef operator()(int idx) const{
return data[idx]; // This is possible due to the same "bug" as with the ref in Bar
}
Ref operator()(int idx){
return data[idx];
}
};
I noticed that //1 does work as expected but //2 does not. Return value is still modifiable. Shouldn't they be the same?
Yes, this is correct behaviour. The type of ref is Foo &. Adding const to a reference type1 does nothing—a reference is already immutable, anyway. It's like having a member int *p. In a const member function, its type is treated as int * const p, not as int const * p.
What you need to do is add const manually inside the const overload if you want it there:
struct Bar
{
Foo& ref;
Bar(Foo& r):ref(r){}
int& operator()(int idx) const{
return const_cast<const Foo&>(ref)(idx);
}
};
To address the question edit: no, the typedefs are not the same. const int & is a reference to a (constant int). const Ref is a constant Ref, that is, a constant (reference to int); parentheses used in mathematical sense.
1 I am talking about the reference type itself. Not to be confused with adding const to the type to which the reference refers.
Yeah, it is expected behaviour. The reason is that const for your method says only that reference wont be change not the referenced object. Reference is always unchanged so it is always true. Take a look at this code with pointer:
int i;
struct Bar
{
int* pi;
Foo& ref;
Bar(Foo& r):ref(r){}
int& operator()(int idx) const{
*pi = 4; // we can change pointed object
pi = &i; // Compile error: we can't change the pointer.
return ref(idx);
}
};