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

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_; }
// ^^^^^^^^^^^^^^^^^^^^^^^

Related

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.

Const and reference member function qualifiers

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.

Why do I need twice the same function?

I am beginner in cpp language.
Recently I saw in a many classes declare twice the same function with a little different such as:
int& operator[](size_t i) {
assert(i<size());
return _buf[i];
}
const int& operator[](size_t i) const {
assert(i<size());
return _buf[i];
}
What is the different between the function? why I need the first one? and in which cases the first function will be work and in which cases the second function will be work?
On of those is const the other isnt. Let me put it in some context:
struct Foo{
int value = 0;
int& operator[](size_t i) {
std::cout << "non-const\n";
return value;
}
const int& operator[](size_t i) const {
std::cout << "const\n";
return value;
}
};
The const version will be called on const instances while the non-const will be called on non-const instances. E.g.
int main(){
Foo f;
int x = f[0];
f[0] = 3; // OK
const Foo g;
int x = g[0];
//g[0] = 3; // NOT OK
}
...will print
non-const
const
Indeed both methods should be the same and the major difference is the const version returning a const reference while the non-const returns a reference that allows to modify the value.
As you correctly observed, apart from the constness and the return type, the two are identical. To avoid duplicate code, sometimes it can make sense to use a small trick and write the const version in terms of the non-const:
const int& operator[](size_t i) const {
std::cout << "const\n";
return const_cast<Foo*>(this)->operator[](i);
}
See here for the full example.
Usually you don't want to let users change your objects somehow if they are marked as const.
It means that if you have a class which provides operator[], you don't want to let users change the internal state of objects of this class via operator[] if these objects are const.
That's why you have two overloads. If the object is const, then the version
const int& operator[](size_t i) const
is called. This version returns const int&, so you can't do any modification.
In opposite, if the object is not marked as const, then
int& operator[](size_t i)
is called and you are free to modify the internal state of the object via the reference returned.
The differences are the const keyword:
int& operator[](size_t i) { (1)
const int& operator[](size_t i) const { (2)
The first function return a reference to the object, which means that you can modify the object (for example by doing foo[0] = bar.
The second use the const keyword twice: const int& means that you return a const reference that you can't modify. The second const is here to specify that this function will not modify the object.
You need those two version because (1) is used when you want to modify an element of the collection and (2) is used on const object:
you can do this:
void foo(std::vector<int> const& v) {
int j = v[0];
}
because vector as an operator that look like (2)
The first overload states that the subscript operator can modify internals of the class instance, the later states that internals of the class instance are read-only and thus, can't be modified.
Effectively, it means that this pointer points to either const or non-const object.
Previously:
You tagged your question with C which is not correct, as C does not offer any class member functions and thus AFAIK, const after the global function declaration is illegal.
It means your class is providing the support for two things,
Non Const Object
Const Object
int& operator[](size_t i) will be called for Non const object, because there is no const qualifier at the end.
const int& operator[](size_t i) const will be called for const object, because there is const qualifier at the end.

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);
}
};

C++11 trailing return member function using decltype and constness

I am trying to understand the trailing return based new function declaration syntax in C++11, using decltype.
In the following code, I am trying to define a member function returning a const & to allow for read-only access to i
#include <iostream>
#include <type_traits>
struct X {
int &i;
X(int &ii) : i(ii) {}
// auto acc() const -> std::add_const<decltype((i))>::type { return i; } // fails the constness test
auto acc() const -> decltype(i) { return i; } // fails the constness test
// const int &acc() const { return i; } // works as expected
};
void modify_const(const X &v) {
v.acc() = 1;
}
int main() {
int i = 0;
X x(i);
modify_const(x);
std::cout << i << std::endl;
return 0;
}
As mentioned in the comments, only the last commented version of acc() works, whereas using the others, the code just compiles and prints value 1.
Question: How do we have to define the acc() function using the new function declaration syntax based on decltype, such that the compilation here fails due to returning a const &int in modify_const, or in other words, such that acc() has a proper const &int return type.
Remark: using int i; instead of int &i; as the member variable in X produces a compile error, as expected.
Edited to better distinguish between constness of v and X::i, respectively. It is the latter I am trying to impose in acc().
The problem is that decltype((i)) return int& and apply const to that type has no effect. You want something like
template <typename T> struct add_ref_const { typedef T const type; };
template <typename T> struct add_ref_const<T&> { typedef T const& type; };
... and then use
auto acc() const -> typename add_ref_const<decltype((i))>::type { return i; }
That is, the const needs to go between the type T and the &. The solution would have been obvious if you had put the const into the correct location: const should go to the right.
There's nothing illegal about a const member function modifying the target of a pointer to non-const, even if that pointer was gotten from a member variable.
From the compiler's perspective, int& IS the correct return type.
Your "modify_const" function is incorrectly named. i is what gets modified, and is not const.
Simply add an & to the left and skip the trailing return type.
struct X {
int &i;
X(int &ii) : i(ii) {}
auto& acc() const { return i; } // Returns const reference
auto& acc() { return i; } // Returns non-const reference
const auto& acc() const { return i; } // Add const to the left to make it even more readable
};
Note that using this syntax you can declare the member variable after you have declared the function.