Copying member variable through const reference to a class - c++

My understanding of const references to a class was that we cannot modify the state of the class i.e. cannot perform any action that modifies any of it's member variables. But consider the following code.
#include <iostream>
#include <vector>
struct Vec {
Vec(std::vector<int>& v) : vec(v) {}
Vec(const Vec& v) : vec(v.vec) {
v.vec.resize(100);
}
std::vector<int>& vec;
};
int main() {
std::vector<int> x;
Vec v1(x);
Vec v2(v1);
v1.vec.resize(10, 0);
v2.vec[5] = 8;
std::cout << v1.vec[5] << std::endl;
return 0;
}
I compiled this code using g++:4.8.3 with -Wall flag and it compiled. I have two questions.
In Vec copy construtor, we pass a const reference to a Vec v. By extension of constness of v, v.vec should also have been a const std::vector<int>&. Then how does it get copied to type std::vector<int> ?
The only logical way the above can happen is if constness of a class doesn't apply to it's member variable. So my question is what implications does a constness of a class has on it's member variables ?

The constness of a class applies to its member variables, but not to the referents of the member variables that are references. This is similar to pointer members, where the pointer would be const, but not what it points to.

In Vec copy construtor, we pass a const reference to a Vec v. By extension of constness of v, v.vec should also have been a const std::vector&. Then how does it get copied to type std::vector ?
The only logical way the above can happen is if constness of a class doesn't apply to it's member variable. So my question is what implications does a constness of a class has on it's member variables ?
That's not really how const extends. You stick the const on the back of the type, not the front. Consider the following code...
struct foo {
foo() {}
int * i;
};
int main (void)
{
foo my_foo;
int * &a = my_foo.i;
const int * &b = my_foo.i;
int * const &c = my_foo.i;
const foo const_foo;
int * &d = const_foo.i;
const int * &e = const_foo.i;
int * const &f = const_foo.i;
return 0;
}
foo.cpp: In function ‘int main()’:
foo.cpp:12: error: invalid initialization of reference of type ‘const int*&’ from expression of type ‘int*’
foo.cpp:16: error: invalid initialization of reference of type ‘int*&’ from expression of type ‘int* const’
foo.cpp:17: error: invalid initialization of reference of type ‘const int*&’ from expression of type ‘int* const’
This shows that const_foo.i has type int * const, which is different from const int *. The type int * const makes no promises about its pointed at data not changing, just the pointer itself can't change.
In your example, v2.vec would have type std::vector<int> & const. But that type is meaningless (and illegal) because you can't change the alias of a reference anyhow. std::vector<int> is already const for this purpose.
It is possible to have const-ness inherit, but you have to explicitly code that rule. The following code will happily refuse to compile due to enforcing const restrictions by restricting callers to use a getter that makes the contract you are looking for...
#include <iostream>
#include <vector>
struct Vec {
Vec(std::vector<int>& v) : _vec(v) {}
Vec(const Vec& v) : _vec(v.vec()) {
v.vec().resize(100);
}
// How to make const-ness inherit...
std::vector<int> & vec() { return _vec; }
std::vector<int> const & vec() const { return _vec; }
private:
std::vector<int>& _vec;
};
int main() {
std::vector<int> x;
Vec v1(x);
Vec v2(v1);
v1.vec().resize(10, 0);
v2.vec()[5] = 8;
std::cout << v1.vec()[5] << std::endl;
return 0;
}
Once you start doing that you get into strange territory however, as it allows me to call std::vector<int> const & vec() const, save a 'data-const' reference to _vec, and then have other code change the data in vec, violating the const contract to the earlier code. There are a number of landmines out there, which may be why the language doesn't have this sort of const inheritance built-in.

Related

C++ Compilation error in overloading

The following code compiles fine.
#include <iostream>
#include <vector>
using namespace std;
class MyClass
{
public:
MyClass()
{
x.resize(2);
x[0] = 10;
x[1] = 100;
}
std::vector<int> getValue()
{
return x;
}
const std::vector<int>& getValue() const
{
return x;
}
private:
std::vector<int> x;
};
int main()
{
MyClass m;
std::vector<int> y = m.getValue();
for(int i=0; i<y.size(); i++)
{
std::cout<<y[i]<<std::endl;
}
const std::vector<int>& z = m.getValue();
for(int i=0; i<z.size(); i++)
{
std::cout<<z[i]<<std::endl;
}
return 0;
}
However, when I change the "std::vector getValue()" to a more correct version (since the function is supposed to change the object) by adding "const" (std::vector getValue() const) it gives the following compilation error.
error: 'const std::vector<int>& MyClass::getValue() const' cannot be overloaded const std::vector<int>& getValue() const
Why is it so?
I have used "gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)"
You can't define two functions with the same name, which differ only in return type. So define function with different name, for example:
std::vector<int> getValueCopy() const;
By adding const to the first function you render calls to getValue ambiguous: What is the difference between those 2 functions:
std::vector<int> getValue() const; // 1
const std::vector<int>& getValue() const; // 2
Well, they are the same, except for the return value, but wait! You can't overload based on the return type in C++! It wouldn't make sense, most calls would be ambiguous:
std::vector<int> y = m.getValue(); // which one? It can be 1, it can be 2 (std::vector<int>
// is not a better match than const std::vector<int>&)
const std::vector<int>& z = m.getValue(); // same as above
m.getValue(); // which one?
But also, what is supposed to be the difference between the two?
The first one is 100% safe, while the second one is not: one could store a reference to x, and it becomes a dangling reference if the object gets destroyed. And so I would say that you get rid of the second one, if possible.
your problem is that you didn't understand functions overloading concept
when you overload function The definition of the function must differ from each other by the types of argument or the number of arguments in the argument list.
You can not overload function declarations that differ only by return type.
in your functions :
std::vector<int> getValue() const
const std::vector<int>& getValue() const
it only differ in return type so it will be not considered as overloaded functions
best way to correct your error is to change your second function name to getValuev2()
or change arguments of one of the functions.
you can read more about overloading in C++ :
https://www.tutorialspoint.com/cplusplus/cpp_overloading.htm

How C++ do overload resolution for this "vector[0] = 1;"

As I know, C++ only have function overload base on the parameter or implicate object parameter. But I find there are two operator[] for vector. And it will select the correct function in following code:
std::vector<int> v;
v[0] = 1; // This will select the non-const version.
return &v[0]; // This will select the const version.
Can anyone explain how this happen?
reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
------Edit 1------
I think it will select the const version, because following cc file cannot compile with clang++ and g++ with following error. Doesn't understand following error. Can anyone explain more?
error: cannot initialize return object of type 'char *' with an rvalue
of type
'const value_type *' (aka 'const char *')
return data_.size() == 0 ? NULL : (&data_[0]);
#include <assert.h>
#include <deque>
#include <vector>
#include <map>
class X
{
public:
X() {
}
virtual ~X() {
}
char* data() const {
return data_.size() == 0 ? NULL : (&data_[0]);
}
size_t size() const {
return data_.size();
}
private:
std::vector<char> data_;
};
Actually in both cases the non-const version is called. The time when the const version would be called would be if the vector was const.
std::vector<int> const v = {1,2,3};
int x = v[0];
In the above case, trying to invoke the non-const version would result in a compiler error
v[0] = 5; // nope can't call non-const version, this is trying to mutate a const variable
Edit
Regarding your example, based on the signature of your function
char* data() const
you have declared that the method data is const, meaning that it shall not attempt to mutate any of the member variables. In other words, all member variables within a const function are treated as const. In the context of a const method, the variable is seen as
std::vector<char> const data_;
Since v is a non-const vector the constversion is never called.

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.

Return reference to a vector member variable

I have a vector as member in a class and I want to return a reference to it through a getVector() function, so as to be able to modify it later. Isn’t it better practice the function getVector() to be const? However I got an error “qualifiers dropped in binding reference of type…” in the following code. What should be modified?
class VectorHolder
{
public:
VectorHolder(const std::vector<int>&);
std::vector<int>& getVector() const;
private:
std::vector<int> myVector;
};
std::vector<int> &VectorHolder::getVector() const
{
return myVector;
}
Since it is a const member function, the return type cannot be non-const reference. Make it const:
const std::vector<int> &VectorHolder::getVector() const
{
return myVector;
}
Now it is okay.
Why is it fine? Because in a const member function, the every member becomes const in such a way that it cannot be modified, which means myVector is a const vector in the function, that is why you have to make the return type const as well, if it returns the reference.
Now you cannot modify the same object. See what you can do and what cannot:
std::vector<int> & a = x.getVector(); //error - at compile time!
const std::vector<int> & a = x.getVector(); //ok
a.push_back(10); //error - at compile time!
std::vector<int> a = x.getVector(); //ok
a.push_back(10); //ok
By the way, I'm wondering why you need such VectorHolder in the first place.
it's not unusual to declare both const and mutable variants, like so:
std::vector<int>& VectorHolder::getVector() {
return myVector;
}
const std::vector<int>& VectorHolder::getVector() const {
return myVector;
}
the underlying problem with your program is that you return a non-const reference from a const method.
std::vector<int>& VectorHolder::getVector() const {
return myVector; // << error: return mutable reference from const method
}
so you make it const using this form:
const std::vector<int>& VectorHolder::getVector() const {
return myVector; // << ok
}
and when this is in a non const method or the client holds a non-const reference, then you can legally use a non-const method:
std::vector<int>& VectorHolder::getVector() {
return myVector; // << ok
}
finally, you could return a value (in some cases):
std::vector<int> VectorHolder::getVector() const {
return myVector; // << ok
}
because the copy requires no mutation and provides no exposure to the internal data.
so you will end up declaring both variants quite often.
the results of declaring both are:
VectorHolder m;
const VectorHolder c;
m.getVector().size(); // << ok
c.getVector().size(); // << ok - no mutation
m.getVector().push_back(a); // << ok
c.getVector().push_back(a); // << error: attempt to mutate const reference because the const vector is returned
so it all works out nicely (apart from the redundancy of the methods).
The function getVector can be declared as const. It returns a reference that can be modified, so while the actual function doesn't modify anything in the class, the caller will be able to modify internal data.
Declare it as:
std::vector<int>& getVector();
If you want a function to return a vector that can't be modified, the use the const modifier on both the vector and the function:
const std::vector<int>& getVector() const;
The reason is that a const member function should only return const references. This is because in a const function, every data member becomes constant.
Therefore you have to declare the getVector() this way:
std::vector<int> &VectorHolder::getVector() const;