Why do I need twice the same function? - c++

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.

Related

Understanding const

If you want to write an iterator, you usually write
T* operator->() const;
My problem is understanding this "const" with pointers and reference.
For instance, you can write the following struct:
struct A{
int* x;
A(int& x0):x{&x0}{}
int* ptr() const {return x;}
int& ref() const {return *x;}
};
And you can use it this way:
int x = 10;
const A a{x};
int& r = a.ref();
int* p = a.ptr();
*p = 4;
cout << x << endl; // prints 4
r = 25;
cout << x << endl; // prints 25
But why this compiles and works right (at least with g++ and clang). Why?
As I defined
const A a{x};
this "a" is const. So when I call
int* p = a.ptr();
I am calling ptr() with a const object, so the internal pointer A->x must be "int * const". But I am returning a "int *" without const. Why is this correct?
And what happens with the reference? If I call A::ref() with a "const A", what's the type this function returns? Something like "int& const"??? <--- I suppose this is the same as "int&".
Thanks for your help.
There is a different between bitwise const and logical const.
When you have a const A, you can't modify its int* member. But there's a difference between modifying the member itself (which int that x points to) and modifying through the member (the value of the int that x points to). Those are different kinds of const. In the simplest case:
struct Ptr { int* p; };
int i = 42;
const Ptr ptr{&i};
*ptr.p = 57;
ptr.p still points to i, nothing changed there, so the const mechanic is enforced. But it's not logically const since you still changed something through a const object. The language doesn't enforce that for you though. That's up to you as the library writer.
If you want to propagate const-ness, you just provide an interface that is logically const:
int const* ptr() const {return x;}
int const& ref() const {return *x;}
// ^^^^^
Now, users can't modify through your const A both bitwise (can't change what x points to) and logically (can't change that value of that pointee either).
But why this compiles and works right (at least with g++ and clang). Why?
Because the program is well formed and has defined behaviour. Const correctness was not violated.
I am calling ptr() with a const object, so the internal pointer A->x must be "int * const". But I am returning a "int *" without const. Why is this correct?
Because it is completely OK to make copies of const objects. Those copies need not to be const. Copying an object does not make modifications to the original object (assuming there is no user defined copy constructor that does silly things).
And what happens with the reference? If I call A::ref() with a "const A", what's the type this function returns?
int& ref() always returns int&. Just like int* ptr() always returns int*.
Something like "int& const"???
There is no such thing like int& const. References cannot have top level qualifiers (they can never be re-assigned).
In struct A, when you make an instance of it const, you make the pointer constant, but that doesn't automatically make the pointed-to object constant.
Declaring something like const A a(ref); is basically equivalent to invoking the following code:
struct A_const {
int * const x;
A(int& x0):x{&x0}{}
int* ptr() const {return x;}
int& ref() const {return *x;}
};
If you remember your pointer rules, this means that x is a constant pointer, which means it cannot be made to point to something else (it's functionally similar to a reference, but can be null), but critically, the int that it is pointing to is not constant, which means nothing stops you from writing something like this:
int val = 17;
const A a(val);
*(a.val) = 19; //Totally valid, compiles, and behaves as expected!
int val2 = 13;
//a.val = &val2; //Invalid, will invoke compile-time error
This is also the reason why std::unique_ptr<int> and std::unique_ptr<const int> represent different objects.
If you want the pointed-to object to not be modifiable on a const object, you need to enforce that in the code itself. Since functions can be overloaded on the basis of whether the source object is const or not, that's pretty easy:
struct A {
int * x;
A(int& x0):x{&x0}{}
int * ptr() {return x;}
int & ref() {return *x;}
int const* ptr() const {return x;}
int const& ref() const {return *x;}
};
int val = 17;
A a(val);
a.ref() = 19;//Okay.
*a.ptr() = 4;//Okay.
const A b(val);
b.ref() = 13;//Compile error
*b.ptr() = 17;//Compile error

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

How does compiler determine which function to use?

As we know compiler determines the function by the overloaded function signature (like parameter types), but how does this one work:
v[i] = 1;
When compiler looks at these two overloaded functions:
const T& operator[](size_t i) const;
T& operator[](size_t i);
How does it determine which one to use? Does the compiler tries to use 1st one, and finds out it does not work, then it tries to use the second one?
If the object is non-const, the non-const version of the function is invoked (if it is available), else const version is invoked. Now see which is which:
const T& operator[](size_t i) const; //CONST MEMBER FUNCTION
T& operator[](size_t i); //NON-CONST MEMBER FUNCTION
An example,
void f(std::vector<int> const &v1, std::vector<int> & v2)
{
std::cout << v1[0] << std::endl; //invokes const version
std::cout << v2[0] << std::endl; //invokes non-const version
}
Now when you write:
v[i] = 1;
If I don't assume v to be std::vector, then it depends on the const-ness of the v. If v is const, then const-version will be invoked, else non-const version will be invoked (if it is available, else v will convert into const object and then const function will be invoked).
The non-const member function cannot be called on a const object.
Hence, in order to be practically useful, the rules have to be that it's called on a non-const object, and that conversely, the const member function is called on a const object.
And that's what the rules are.

Same declaration with and without const, why?

If I have
T& operator[](int i) const;
const T& operator[](int i) const;
how can I use one instead of another one? Why would one define these?
I don't think you can - they are ambiguous. Typically you'd want this:
T& operator[](int i); // note no trailing const
const T& operator[](int i) const;
The first overload is selected when your object is non-const - it's a better match than the second, and the second is selected when the instance is const - the first doesn't match at all.
The correct pair is this (I think your post has typo, as you seem to mean the following):
T& operator[](int i); //without const;
const T& operator[](int i) const;
As for your question, I would suggest you to define both of them, because you may end up using both in your code. Here is one example.
void f(X const &a, X & b)
{
int i = get_index();
std::cout << a[i] << std::endl; //this invokes const version
std::cout << b[i] << std::endl; //this invokes non-const version
}
In this code, a[i] invokes const-version of operator[], because a is a const object, which means it cannot invoke non-const version. However, in case of b[i], the C++ rules dictate that it should invoke non-const version, because the object b is non-const and there exists a non-const overload of operator[], so that is preffered. In the absence of non-const overload, even b[i] would invoke const version which is not desirable on many occasions, such as when you would like to write data at index i, as:
b[i] = item; //it must invoke non-const version in order
//to compile and work (properly).
Hope that helps.

Call nonconst member version from const [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I remove code duplication between similar const and non-const member functions?
i have two members
A &B::GetA (int i)
{
return *(m_C[m_AtoC[i]]);
}
const A &B::GetA (int i) const
{
return *(m_C[m_AtoC[i]]);
}
for now i just duplicate code, but may be there exists nice way to do it. I certanly dont want to deal with type cast from const to non-const.
EDIT: So i want to call one member frm another to avoid code duplication.
[Note the correction; sorry for getting it the wrong way round initially.]
This is an appropriate situation for using a const_cast, and it allows you to deduplicate code by forwarding the call from the non-const function to the corresponding const function:
A & B::GetA(int index)
{
return const_cast<A &>(static_cast<B const *>(this)->GetA(index));
}
It's important to note that this should only be done in one direction: You can implement the non-const method in terms of the constant one, but not the other way round: The constant call to GetA() gets a constant reference to the object in question, but since we have additional information that it's actually OK to mutate the object, we can safely cast away the constness from the result.
There's even a chapter on precisely this technique in Scott Meyer's Effective C++.
You could do something like:
class B {
public:
A& GetA (int index) { return GetA_impl(index); }
const A& GetA (int index) const { return GetA_impl(index); }
private:
A& GetA_impl (int index) const { return *(m_C[m_AtoC[i]]); }
};
I'm not sure it's really worth the effort in this case, but this can be useful if the potentially duplicated code gets more complicated.
You can avoid const_cast with a little template metaprogramming.
// This meta function returns a const U if T is const, otherwise returns just U.
template <typename T, typename U>
make_same_const<T, U>
{
typedef typename std::conditional<
std::is_const<T>::value,
typename std::add_const<U>::type,
U
>::type type;
};
// Generic version of a function. Constness of return type depends on
// constness of T.
// This is a static member template of B.
template <typename T>
make_same_const<T, A>::type& B::GetA(T& obj, int i)
{
return *(obj.m_C[obj.m_AtoC[i]]);
}
A& B::GetA(int i) { return B::GetA(*this, i); }
A const& B::GetA(int i) const { return B::GetA(*this, i); }
IMO this isn't enough code (or complexity) to be worth de-duplicating.
As you can see in both the const_cast-based solutions, the cast expression is actually longer than the original code.
If you have a longer or more complex expression you're really worried about, though, please show it.
Assuming the bodies of GetA() and GetA() const are identical (which means GetA() doesn't modify *this), you can safely use one const_cast to implement the const version using the non-const one:
const A& B::GetA() const {
return const_cast<B*>(this)->GetA();
}
The non-const GetA() doesn't modify the object, so calling it on a const B object is not undefined. The non-const reference returned by non-const GetA() is converted to a const& before being returned out of GetA() const, so there's no danger of undefined behaviour there either.
How about
const A &B::GetA (int index) const
{
return *(const_cast<B*>(this)->GetA(index));
}