views:

109

answers:

3

in the following code ( taken from effective C++ ):

class A 
{
  ....
  ....
  ....

char& operator[](std::size_t position)         // now just calls const op[]
  {
    return
      const_cast<char&>(                         // cast away const on

                                                 // op[]'s return type;

        static_cast<const TextBlock&>(*this)     // add const to *this's type;

          [position]                            // call const version of op[]

      );
  }

  const char& operator[](int index) const
  {
     ...
     ...
     ...
  }
}
//complete example, tested with VC 2010
#include<iostream>
#include<string>

class TextBlock
{
public:
    TextBlock(std::string st):text(st){};
    TextBlock(char* cstr): text(cstr){};
    TextBlock(const TextBlock& r)
    {
        std::cout<<"copy constructor called"<<std::endl;
    }
    char& operator[](int index)
    {
        std::cout<<"non-const operator"<<std::endl;
        return const_cast<char&>(static_cast<const TextBlock>(*this)[index]);
    }

    const char& operator[](int index) const
    {
        std::cout<<"const operator"<<std::endl;
        return text[index];
    }

private:
    std::string text;
};

int main()
{
    TextBlock rt("hello");
    std::cout<<rt[0]<<std::endl;
}

In this code, if you change the static_cast from const TextBlock& to const TextBlock, this results in non-const version of operator[] getting called recursively. Can anyone explain what's the reason behind this ( why const TextBlock results in not calling const member function operator[] ).

A: 

char& operator[](std::size_t position) and char& operator[](std::size_t position) const are different. Note the 'const' after the function declaration. The first one is the 'non-const' version of the operator, while the second is the const version of the operator. The non-const operator function gets called when the instance of that class is non-const and const version gets called when the object is const.

const A a;
char c = a[i]; // Calls the const version
A b;
char d = b[i]; // Calls the non-const version

When you say (*this)[position] inside the non-const version of the operator, it calls the non-const version, which again calls the non-const version of the operator and it becomes an infinite loop. By doing that casting you're essentially calling the const version of the same operator.

EDIT: Hmm.. seems like the problem is not as it seems. Do you have a copy-constructor for that class? I'm guessing it's that which is causing this issue.

EDIT2: I think I've got it. a[i] -> creates temporary variable upon const cast without reference (const A) -> temp[position] -> creates temporary variable upon const cast without reference -> temp2[position] -> and it continues.....
while the one with const cast with reference (const A&) the temporary variable is not created, hence escaping the death-loop.

To make it more clear... static_cast<const TextBlock>(*this)[position]; Break-down of the above statement would be:

  • Convert *this to const TextBlock
  • Create a temporary variable to hold the const TextBlock (copy-constructor called passing const TextBlock)
  • Call the operator[] on the temporary variable, which is NOT const because it was temporary.
  • Temporary variable goes through the above process.
Vite Falcon
I made a mistake in the initial posting, i forgot to mark the second operator [] as a const member function.
The whole point is that why would it call the non-const version of the operator[]. If it calls the const-version, it'll only create a temporary once; it should not do it recursively (check the codepad example in my other comment, it demonstrates that this is NOT what happens). It only calls the non-const version of the operator [] once.
reko_t
@reko_t: Just broke down the whole statement for clarity.
Vite Falcon
That being said, there's absolutely no reason one should do the cast without the reference anyways, since as you said it'd result in a temporary copy which is just plain pointless. And it can also cause very weird behavior, since the operator[] is returning a reference to a char, after a temp copy, that reference might not even be pointing to the original object's memory.
reko_t
"Call the operator[] on the temporary variable, which is NOT const because it was temporary." Why wouldn't the temporary variable be a const? It clearly is const, if it wasn't, this wouldn't happen: http://codepad.org/uZ1q4yNu
reko_t
Here's a more clear example: http://codepad.org/cMMW8SFe As you can see, it does call the const version of operator[] after creating the temporary. My guess is that this might work differently in different compilers, although I don't think something like this should be undefined behavior.
reko_t
@reko_t: Yup, you're right. Tested it in VS2008.
Vite Falcon
A: 

The code below works - with the return values changed to char to avoid the issue reko_t found with returning a reference to an already-gone temporary - on g++ 3.4.6.

To explain the problem...

static_cast<const TextBlock>(*this)

...is effectively the same as...

const TextBlock temporary = *this;

...which you then index into and return a reference. But, that temporary is gone from the stack by the time that reference is used. Given you were returning such a reference, your behaviour was technically undefined.

Does your compiler work with the code below? (type of position standardised at int to avoid ambiguity).

#include <iostream>

struct A  
{ 

  char operator[](int position)         // now just calls const op[] 
  { 
    return 
        static_cast<const A>(*this)     // add const to *this's type; 
          [position];                   // call const version of op[] 
  } 

  const char operator[](int index) const 
  { 
    return x_[index];
  } 

  char x_[10];
};

int main()
{
  A a;
  strcpy(a.x_, "hello!");
  const A& ca = a;
  std::cout << a[0] << ca[1] << a[2] << ca[3] << a[4] << ca[5] << '\n';
}
Tony
Tried this in visual C++ ( 2010 ), won't compile. "error C2440: 'const_cast' : cannot convert from 'const char' to 'char "
Tony
A: 

Your operators have different inparameter types

char& operator[](std::size_t position)
const char& operator[](int index) const <- Should also be std::size_t

This might be the solution you are looking for. Did the example from the book have different types for the inparameter? Remember that type casting works on the return value.

char& operator[](std::size_t index)
{
  std::cout<<"non-const operator"<<std::endl;
  const TextBlock &  ref = *this;
  return const_cast<char&>(ref[index]);
}
Per-Åke Nilsson