Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour with operator[]

Tags:

c++

Having spent the last couple of days experimenting with C++ operator[] methods, I've come across an anomaly that I can't easily explain. The code below implements a simple test string class, which allows the user to access single characters via its subscript "operator" method. As I would like to differentiate between lvalue and rvalue subscript contexts, the "operator" method returns an instance of the custom "CReference" class as opposed to a standard C++ character reference. Whilst the "CReference" "operator char()" and "operator=(char)" methods appear to handle each rvalue and lvalue context respectively, g++ refuses to compile without the presence of an additional "operator=(CReference&)" method as documented below. Whilst the addition of this method appears to placate some kind of compile-time dependency, it is never actually invoked at run-time during the execution of the program.

As someone who thought they had acquired a fundamental understanding of C++ intricacies, this project has certainly proved to be a humbling experience. If anyone could see their way to enlightening me as to what's going on here, I would be eternally grateful. In the meantime, I'm going to have to pull out the C++ books in order to reconcile the void** between what I know and what I think know.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// Class for holding a reference to type char, retuned by the "operator[](int)"
// method of class "TestString". Independent methods permit differentiation
// between lvalue and rvalue contexts.
class CReference
{
 private:
   char& m_characterReference;

 public:
   // Construct CReference from char&
   CReference(char& m_initialiser)
     : m_characterReference(m_initialiser) {}

   // Invoked when object is referenced in a rvalue char context.
   operator char()
   {
    return m_characterReference;
   }

   // Invoked when object is referenced in a lvalue char= context.
   char operator=(char c)
   {
    m_characterReference = c;
    return c;
   }

   // NEVER INVOKED, but will not compile without! WHY???
   void operator=(CReference &p_assignator){}
};


// Simple string class which permits the manipulation of single characters
// via its "operator[](int)" method.
class TestString
{
 private:
   char m_content[23];

 public:
   // Construct string with test content.
   TestString()
   {
    strcpy(m_content, "This is a test object.");
   }

   // Return pointer to content.
   operator const char*()
   {
    m_content[22] = 0;
    return m_content;
   }

   // Return reference to indexed character.
   CReference operator[](int index)
   {
    return m_content[index];
   }
};


int main(int argc, char *argv[])
{
 TestString s1;

 // Test both lvalue and rvalue subscript access.
 s1[0] = s1[1]; 

 // Print test string.
 printf("%s\n", (const char*)s1);
 return 0;
}
like image 294
Taliadon Avatar asked Jul 08 '11 20:07

Taliadon


3 Answers

  1. The line s1[0] = s1[1]; causes the compiler to generate an implicit copy assignment operator for CReference if you didn't declare one yourself. This causes an error because your class has a reference member, which can't be copied.

  2. If you added an assignment operator that takes a parameter of type const CReference&, it would get called by the assignment.

  3. In your code, you declared a copy assignment operator of type void operator=(CReference &p_assignator). This can't be called because the righthand side of the assignment is a temporary object, which can't be bound to a non-const reference. However, the act of declaring this operator causes the compiler not to try to define an implicit copy assignment operator, and therefore avoids the previous compilation error. Since this operator can't be called, the compiler goes for the other assignment operator that takes a parameter of type char.

like image 180
interjay Avatar answered Oct 31 '22 15:10

interjay


What's happening is that without the definition of operator=(CReference&),there are two possible overloads of operator=: the implicitly-defined operator=(const CReference&) and your operator=(char). When it tries to compiled s1[0] = s1[1], it tries to find the best match for operator=, which is the implicitly-defined operator=(const CReference&). But, that isn't allowed to be implicitly-defined, because CReference contains a reference member, so you get an error.

Conversely, when you do define operator=(CReference&), there's no longer an implicitly-defined assignment operator. Sections 12.8 clauses 9-10 of the C++03 standard state:

9) A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.109)

10) If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. The implicitly-declared copy assignment operator for a class X will have the form

X& X::operator=(const X&)

if
— each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and
— for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M.110)

Otherwise, the implicitly declared copy assignment operator will have the form

X& X::operator=(X&)

So, when it tries to compile s1[0] = s1[1], it has two overload choices: operator=(CReference&) and operator=(char). It choose operator=(char) as the better overload because it can't convert a temporary object (the result of s1[1]) into a non-const reference.

like image 3
Adam Rosenfield Avatar answered Oct 31 '22 16:10

Adam Rosenfield


I compiled your program on Ideone by removing the overloaded = and it gives the following error:

prog.cpp: In member function ‘CReference& CReference::operator=(const CReference&)’:
prog.cpp:9: error: non-static reference member ‘char& CReference::m_characterReference’, can't use default assignment operator
prog.cpp: In function ‘int main(int, char**)’:
prog.cpp:70: note: synthesized method ‘CReference& CReference::operator=(const CReference&)’ first required here 

As the error points out,

s1[0] = s1[1];

needs the copy assignment operator for CReference class.

You have a reference member variable in your CReference class

char& m_characterReference;

For classes with a reference member, you need to provide your own implementation of = operator as a default = cannot infer what to do with the reference member. So you need to provide your own version of the = operator.

So a little bit of speculation here:

With,

s1[0]=s1[1]

Ideally, for CReference& CReference::operator=(const CReference&) to be invoked the argument should be a const CReference& but the argument s1[1] returns a non constant CReference&, so:

  1. why does the compiler asks for CReference& CReference::operator=(const CReference&) in the code sample link I posted &
  2. Why does the error go away if we provide a = operator with a non constant argument, like CReference& CReference::operator=(CReference&)

As it seems in the absence of an explicit non constant argument copy constructor the compiler does some kind of a optimization and treats the argument of s1[1] as an constant thus asking for the constant argument copy assignment operator.

If a non constant argument copy assignment operator is explicitly provided the compiler doesn't perform its optimization but just uses the one that is already provided.

like image 1
Alok Save Avatar answered Oct 31 '22 16:10

Alok Save