I got an assignment to implement a template array class.
One of the requirement is to overload the [] operator.
I made this two const and non-const version which seems to be working fine.
const T& operator[](const unsigned int index)const
and
T& operator[](const unsigned int index)
My question is how will the compiler know which one to run
when i will do something like:
int i=arr[1]
On a non-const array?
The non-const function will always be called on a non-const array, and the const function on a const array.
When you have two methods with the same name, the compiler selects the best-fitting one based on the type of the arguments, and the type of the implicit object parameter (arr).
I just answered a similar question the other day, you may find it helpful: https://stackoverflow.com/a/16922652/2387403
It all depends on your declaration of the object. If you have
const T arr[];
...
int i=arr[1];
Then the const version will be called, but if you have
T arr[];
...
int i=arr[1];
Then the non-const version will be called. So in the example you gave, since it was a non-const array, the non-const version will be called.
Related
I've read about the various places to put const. Some usages seem clearly useful to me. Others however evade me. It would be really helpful if someone could confirm or correct my understanding as I explain my mental model of things.
These are the same. I'm not sure I understand why this would ever be useful, though. Does it perhaps allow one to initialize const int variables with a function, which in turn allows some compiler optimizations?
const int foo();
int const foo();
These are the same. The returned pointer cannot be used (via dereferencing) to change the values pointed to.
const int * foo();
int const * foo();
This means the returned pointer itself cannot be changed. But, why would it matter if the caller essentially decides to ignore the returned pointer and set it to something else? Is this only really useful if the pointer is returned by reference?
int * const foo();
These are the same. It means you can only pass in const ints, which allows the compiler to optimize things.
int foo(const int foo);
int foo(int const foo);
This means the passed-in pointer cannot be changed. I'm wondering here too, why would it matter unless the pointer is being passed in by reference?
int foo(int * const foo);
This (as a member function) guarantees that the function won't change the state of the object. Also, if the object itself is declared const, then it will only be able to call such functions.
int foo(int foo) const;
const int as return type is pointless, because in an expression the type of a non-class prvalue result of a function call will be stripped of its top-level const anyway. There is no distinction between a const and non-const prvalue of a non-class type. So the type of foo() is just int, no matter whether it is declared to return const int or int.
It would be different if the return type was a const qualified class type, e.g. const std::string foo(). In that case the const would disallow calling non-const-qualified member functions directly on the result of foo(), e.g. foo().resize(42). Still, this is a very rarely used distinction. And as noted in the comments, under certain conditions it can prevent move operations. E.g. in the above if we have a std::vector<std::string> v;, then v.push_back(foo()) will cause a copy, rather than a move, of the returned string into the vector.
However, the const qualifier is part of the return type in the function type and therefore it is technically possible to differentiate a function declared with const return type from one without it. The type of int foo(int foo) is int(int), but the type of const int foo(int foo) is const int(int). (However overloading based on return type is not possible for non-template functions anyway. The return type is not part of the function signature.)
correct
Same as 1. The type of foo() is simply int*.
The top-level const in the function parameter does not affect the type or signature of the function (in contrast to 1. where it does affect the type). So int foo(const int foo); and int foo(int foo); declare the same function and both have type int(int). Top-level const also doesn't affect how a variable can be initialized, so it doesn't make sense to say "you can only pass in const int". There are no const-qualified prvalues of type int anyway and if int foo can be initialized from some expression, then so can const int foo. The const has no implication on initialization or overload resolution. However const can be used like this in a function definition to tell the compiler and yourself that this parameter is not intended to be modified in the definition.
Same as 4.
This is the correct idea, although in the details it is not strictly true. Rather the const is only relevant to overload resolution (behaving as if the implicit object parameter was a const reference) and the type of this (which will be a pointer to const). It is still possible to mutate members declared as mutable or to use const_cast to mutate members. It is also not relevant whether the object itself is const, only whether the glvalue through which the member function is called is.
Can I have two overloaded operator[] like this in the same class?
I am confused which definition is used when I use operator[], isn't int ambiguous? Don't they have the same signature?
template <class T, int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP() {};
virtual T & operator[](int i);
virtual T operator[](int i) const;
};
This class contains declarations of these overloaded operator. I have not included definition in my question, though.
Overloaded operators works no different than normal overloaded functions. It's just they are special function. So I'm giving you general example from function same applies to any kind of functions.
As you must know that, top-level const has no effect on the
objects that can be passed to the function. A parameter that has a top-level const is
indistinguishable from one without a top-level const:
Record lookup(Phone);
Record lookup(const Phone); // redeclares Record lookup(Phone)
Record lookup(Phone*);
Record lookup(Phone* const); // redeclares Record lookup(Phone*)
In these declarations, the second declaration declares the same function as the first.
On the other hand, we can overload based on whether the parameter is a reference
(or pointer) to the const or nonconst version of a given type; such consts are
low-level.
// functions taking const and nonconst references or pointers have different parameters
// declarations for four independent, overloaded functions
Record lookup(Account&); // function that takes a reference to Account
Record lookup(const Account&); // new function that takes a constbreference.
Record lookup(Account*); // new function, takes a pointer to Account
Record lookup(const Account*); // new function, takes a pointer to const
In these cases, the compiler can use the constness of the argument to distinguish
which function to call. Because there is no conversion from const,
we can pass a const object (or a pointer to const) only to the version with a
const parameter. Because there is a conversion to const, we can call either
function on a nonconst object or a pointer to nonconst. However, the compiler will prefer the nonconst versions when we pass a
nonconst object or pointer to nonconst.
Examples from primer.
If I understand correctly, a member function that is not supposed to modify the object should be declared as const to let the users know about the guarantee. Now, what happens when that member function returns the reference to *this? For example:
class C{
public:
C &f() const {return *this;}
};
Inside C::f(), this has the type const C*, so, the following will not compile:
int main() {
C c; // non-constant object!
c.f();
return 0;
}
Of course, we could provide a non-const version of C::f() to work on non-constant objects:
class C{
public:
const C &f() const;
C &f();
};
However, I do not believe that this is what we want. Note that the non-constant version does not change the object, but this promise to the users is not expressed... Am I missing something?
EDIT: Let me just summarize the question for clarity: f() does not modify the object on which it is called, so declaring it as C &f(); without making it a const member is misleading. On the other hand, I do want to be able to call f() on non-const objects. How do I resolve this situation?
EDIT: It comes out from all the discussion that took place in the comments that the question was based on an incorrect understanding of what constness of a function member implies. The correct understanding that I am taking away for myself is:
A member function that returns a non-const reference is intended to allow its users to change the object through the returned reference. Therefore, even though this function does not change the object by itself, there should be no inclination to declare it to be a const member!
Your problem is the original function definition:
C &f() const {return *this;}
here you return a non-const reference to the const object, which would allow changing the const object and would be dangerous, therefore it's forbidden.
If you were to write it as
const C &f() const {return *this;}
would be callable from both const and non-const objects and would always return a const reference.
Of course, we could provide a non-const version of C::f() to work on non-constant objects. However, I do not believe that this is what we want.
Probably that is exactly what you want. It's the only way to return a non-const reference when called on non-const objects and keep const correctness when calling it on const objects.
Following is the test code:
struct A
{
operator int ();
operator int () const;
};
void foo (const int);
Now, upon invoking:
foo(A()); // calls A::operator int()
Why does it always chooses the non-const version ? Even making operator const int () const; doesn't have any effect on invoking foo(). Apart from standard reference, can someone explain logically, the reason behind it ?
A() gives you a temporary A object that is not const-qualified. The A() expression is an rvalue expression, yes, but that does not make the A object const-qualified.
Since the A object is not const-qualified, the non-const operator int() is an exact match and the const operator int() requires a qualification conversion, so the non-const overload is selected as the best match.
If you want it to be const-qualified, you'd need to explicitly request a const-qualified A:
foo(identity<const A>::type());
where identity is defined as
template <typename T>
struct identity { typedef T type; };
Note that there is really no difference between operator const int() const and operator int() const: the result is an rvalue and only class-type rvalues can be const-qualified (int is not a class type).
Note also that there is no difference between the void foo(const int) that you have and void foo(int). Top-level const-qualifiers on parameter types do not affect the type of the function (i.e., the type of both of those declarations is void foo(int)). Among other reasons, this is because it doesn't matter to the caller whether there is a top-level const-qualifier; it has to make a copy regardless. The top-level const-qualifier affects only the definition of the function.
James McNellis’ answer really covered it all, but it doesn’t hurt (I hope) with more explanations.
So.
When you call …
o.operator int()
… then the overload selection depends entirely on the constness of o.
Nothing else.
To see why, consider this class:
struct Bar
{
void f() {}
void f() const {}
};
Technically those member functions do not need to be member functions. They could just as well have been chosen to be free standing functions. But then they need Bar argument:
struct Bar
{};
void f( Bar& ) {}
void f( Bar const& ) {}
And hopefully now it's easier to see that when you do
Bar o;
f( o );
then the first function can be selected. And so it is. Because if the second function was selected, then you could never get the first one. Because if you make the object const, then it would break const correctness to select the first one. So when the object is const only the second can be selected, hence, when it is not const the first one is selected.
In short, the only practical alternative to this rule would be to always select the second one, which would make the first one rather useless, yes?
Cheers & hth.,
One rule you have to remember about C++: it never takes into account the value that is being returned when it selects an overload. In this case since the operator int function takes no parameters, it can't use the parameter list to narrow down the choices either. All it can use it the constness of the object that it's being called from. Since this is a new temporary object, it's not const, so it doesn't choose the const overload.
this is my generic class:
template<class T, class PrnT>
class PersonalVec {
public:
PersonalVec();
T &operator[](int index) const;
const T &operator[](int index) const;
private:
std::vector<T> _vec;
};
I'm required to implement 2 versions of [] operator:
one that will return a const reference and a regular one that will also return a reference.
When i compile it i get:
PersonalVec.hpp:23: error: ‘const T& PersonalVec<T, PrnT>::operator[](int) const’ cannot be overloaded
PersonalVec.hpp:22: error: with ‘T& PersonalVec<T, PrnT>::operator[](int) const
I've put either one of them as remark and then it does compile, so i guess they are colliding somehow. What is the problem and how can i fix it?
thank you!
You need:
T &operator[](int index);
const T &operator[](int index) const;
i.e. non-const operator returns non const reference and const one returns the const reference.
You can't overload based on return type, you can only overload based on parameter types, including the hidden this parameter for member functions.
The type of a function call expression, or an expression involving a potentially overloaded operator, is determined by the function type chosen by overload resolution, you cannot force such an expression to have a particular type and try to influence the overload resolution from the return type.
You need to either give your overloaded functions signatures that differ by parameter types or the constness of this, or you need to pick one appropriate return type and have a single function.
You will need to remove constness of the function when returning the non-const reference.
T &operator[](int index);
const T &operator[](int index) const;
Overloading does not and can not happen on the return type.
Return type of a function is not a criteria that can be used for overloading of functions.
A function can be overloaded if:
1. Different no of arguments
2. Differnt Sequence of arguments or
3. Different types of arguments
You are trying to overload the function based on return type and hence it gives the error.
The 'const' keyword can help you overload functions even if the above 3 criterias are not met. So simple solution can be to make one of the function const and keep other as a normal function