4

I define two versions of overloaded operator[] function in a class array. ptr is a pointer to first element of the array object.

int& array::operator[] (int sub) {  
   return ptr[sub];
} 

and

int array::operator[] (int sub) const { 
  return ptr[sub];
}

Now, if I define a const object integer1 the second function can only be called..... but if I make a non-const object and then invoke as below:

cout << "3rd value is" << integer1[2];

which function is called here?

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
AvinashK
  • 3,309
  • 8
  • 43
  • 94
  • 3
    For a `const` object or access path, the `const` function is invoked. Otherwise, non-const is invoked. `array` might not be a good choice of class name because there is a `std::array` template in the new C++11 standard. – Potatoswatter Apr 23 '11 at 04:45
  • Just an observation. It is better if you return const reference from the const version. See the declaration of *std::vector::operator[]* for an example. – pic11 Apr 23 '11 at 10:00

2 Answers2

2

Your const version should return const int& not int, so that the semantics are just the same between the two functions.

Once you've done that, it doesn't matter which one is used. If the const version has to be used because your object has a const context, then it will be... and it won't matter as you're not trying to modify anything. Otherwise, it'll use the non-const version... but with just the same effect.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    I disagree. I see no benefit in returning a `const int&` compared to an `int`. – fredoverflow Apr 23 '11 at 06:45
  • @FredOverflow: Symmetry, is all. And I would not expect to be able to modify the result of `operator[]` on a `const` object (even if it's just a copy): that would be misleading. – Lightness Races in Orbit Apr 23 '11 at 16:18
  • 1
    You cannot modify the result of a function that returns a scalar by value. You get back a *value*, not a temporary object. – fredoverflow Apr 23 '11 at 17:32
  • @FredOverflow: What? If I have `T const obj;` and I can write `obj[3] = 4;` because I'm using an `op[]` that returns by value, then that's confusing. I'd expect `op[]` to return a `ref-to-const` so that I can't perform that assignment. – Lightness Races in Orbit Apr 23 '11 at 18:09
  • @FredOverflow: Actually, I guess that wouldn't be an lvalue, would it? So I couldn't do the assignment and it wouldn't matter.. hmm. (I still like the symmetry of returning `ref` from non-`const` `op[]`, and `ref-to-const` from `const` `op[]`, though.) – Lightness Races in Orbit Apr 23 '11 at 18:10
  • You could do the assignment if you returned a class type, but `int` is a scalar. – fredoverflow Apr 23 '11 at 19:06
  • It should be a `const T&`, because class types may not be copyable or copying may be expensive. – Puppy Apr 23 '11 at 19:26
  • @DeadMG: Agreed. Unless you know for a fact that `T` will be a scalar, prefer `T const&`. – Lightness Races in Orbit Apr 23 '11 at 19:48
  • @FredOverflow I agree with Tomalak, because there's this: #1 `array a; a[0] = 0; int& i = a[0]; a[0] = 5; /* i is now 5 */`, #2 `array a; a[0] = 0; const int& i = static_cast(a)[0]; a[0] = 5; /* i is now 5 */` I think #1 and #2 should behave the same. – GManNickG Apr 23 '11 at 20:28
  • 1
    I'm with @Fred on this. It's just like it is with [function parameters](http://stackoverflow.com/questions/2139224/2139254#2139254): you pass by const reference unless they are built-ins, which are passed by value. Temporaries of built-in have very little detectable difference to const references (both can't be modified, both bind to a const ref), and returning them might be more efficient than returning a reference. So for the sake of symmetry (with function parameters), `operator[]() const` [should return built-ins by value](http://stackoverflow.com/questions/4421706/4421719#4421719). – sbi Apr 23 '11 at 23:05
  • @GMan: That's a pretty nasty piece of code. If I have a `const T&`, it might refer to an actual lvalue or it might refer to an rvalue. You never know, and you can't rely on either assumption. The same goes for `const T& foobar = foo.bar()`: Might `foobar` change through any side-effects in code between its initialization and any later evaluation? You don't know unless you look closely at `Foo::f()`. And whether that returns a reference to an lvalue or an rvalue is usually considered an implementation detail. – sbi Apr 23 '11 at 23:12
  • @sbi It's nasty yes, but if we consider `const int&` and `int` equal, it might just tip the scales to prefer the former. But perhaps I agree, relying on such stuff is bad design anyway. – GManNickG Apr 24 '11 at 03:25
  • @Dead: But we are *not* talking about class types, we are talking about `int` :) Of course you are right, in the general case it should be `const T&`, but we know that `int` is copyable, so returning by value is fine. – fredoverflow Apr 24 '11 at 07:54
  • @sbi: I think that symmetry between functions that are defined _right next to each other_, do _the same thing_ and _have the same name_ is more important than symmetry with a different part of the language. – Lightness Races in Orbit Apr 24 '11 at 11:29
  • @Tomalak: Symmetry is in the eye of the beholder. One could argue that `int& f()` and `int f() const` are just as symmetric. Or that return type and parameters are _very_ close. I'm fine with your POV onto this, but you will have to accept that it's not the only one. – sbi Apr 24 '11 at 18:59
  • @Tomalak: I know. This is what triggered my arguing. `:)` – sbi Apr 24 '11 at 20:28
2

In your second example, the non-const version will be called, because no conversion is required, and a call that requires no conversion is a better match than one that requires a conversion.

Ultimately, however, you have a basic problem here: what you really want is behavior that changes depending on whether you're using your object as an rvalue or an lvalue, and const doesn't really do that. To make it work correctly, you normally want to return a proxy object, and overload operator= and operator T for the proxy object:

template <class T>
class myarray { 
    T *ptr;

    class proxy { 
        T &val;
        proxy &operator=(proxy const &p); // assignment not allowed.
    public:
        proxy(T &t) : val(t) {}
        operator T() const { return val; }
        proxy &operator=(T const&t) { val = t; return *this; }
    };

    proxy const operator[](int sub) const { return proxy(ptr[sub]); }
    proxy operator[](int sub) { return proxy(ptr[sub]); }
    // obviously other stuff like ctors needed.
};

Now we get sane behavior -- when/if our array<int> (or whatever type) is const, our operator[] const will be used, and it'll give a const proxy. Since its assignment operators are not const, attempting to use them will fail (won't compile).

OTOH, if the original array<int> was not const, we'll get a non-const proxy, in which case we can use both operator T and operator=, and be able to both read and write the value in the array<int>.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • This has the same problem as the original solution- if you want to do, say, `object[1].swap(object[2]);`, firstly, the syntax will be wrong because `proxy` does not define that function, even if `T` does, and secondly, you're needlessly copying `object[2]` because you don't return a reference- even if `proxy` is non-const. – Puppy Apr 23 '11 at 19:29
  • coffin what is the conversion, u are talking about in const version – AvinashK Apr 24 '11 at 03:42
  • @avinash: conversion from `myarray &` to `myarray const &`. It can be done implicitly, but it's still not as good of a match as doing no conversion. – Jerry Coffin Apr 24 '11 at 03:52