Solved

Use difference functions on RHS and LHS of assignment

Posted on 2000-04-11
15
709 Views
Last Modified: 2012-06-21
I recall seeing somewhere how you can create two overloaded functions, 1 called on the LHS and the other on the RHS of an assignment.

I want an 'operator[](...)' function that only references existing items when its used on the RHS of an assignment, but another one (obviously the same name etc) that can create a new entry if there is no matching item found when used on the LHS of an assignment.

I think it has something to do with the placement of 'const' keywords, but I can't make it work and I can't find any references on the web.

Thanks

Don
0
Comment
Question by:mackayd
  • 7
  • 6
  • 2
15 Comments
 
LVL 9

Expert Comment

by:jasonclarke
Comment Utility
You can define const and non-const versions of operator [] on the same thing, which is probably what you are referring to.  

An example:

class X {
public:
//...
   Element& operator[](size_t i);
   const Element& operator[](size_t i) const;
//...
};

However I am don't think this will do what you want since it will just call the operator based on the constness of the calling object.

You can overcome this problem using proxy classes, but that requires a bit more work.  Scott Meyers describes this approach if you have his book.

0
 
LVL 3

Expert Comment

by:Try
Comment Utility
"jasonclarke" has it right!!!  The example he gave is CORRECT for what you want.

Using the example he has given, non-const object 'Element&' will allow subscripts as lvalues in expressions and eliminate calls to copy initializers.

For constant object, 'const Element&' makes subscripts legal ONLY as rvalues; using it as lvalues for constant objects will generate compilation errors (though Scott Meyers shows you how to get around this also).

const String  s = "Hello";
cout << s[0];   // rvalue - displays 'H'
s[0] = 'J';     // error - cannot use as lvalue due to const

String  a = "hello",  b = "jello";
a[0] = b[0];  // lvalue and rvalue
a[0] = 'H';   // lvalue
a[99] = 'M';  // lvalue - out of bounds
cout << a[99] << endl;   // rvalue - out of bounds
0
 
LVL 9

Expert Comment

by:jasonclarke
Comment Utility
The problem with it is that if you call the operator as an rvalue on a non-const object, the non-const operator[] will be called.  So:

String s;

cout << s[0];  // non_const [] will be called

This may or may not be a problem.  

It might be a problem, for example, if you are doing this for efficiency and providing a more efficient const method that has to do less work.

An excellent example of this is a reference counted string class.  When [] is called for an lvalue, the target string must be copied before the write can be done,  but if used on an rvalue the copy is not required.  
0
 
LVL 3

Expert Comment

by:Try
Comment Utility
If I am not too off the mark, the idea is to have two overloaded 'operator[]' functions:  one for LHS activities, the other for RHS activities.  Next, the idea is to have the compiler automatically call whichever is the proper one for the activity at hand.

When the activity involves a constant object, the compiler will call the RHS one, and when the activity involves a non-constant object, the compiler will call the non-const overloaded version.  What's wrong with that?

The main idea is for the programmer to provide a version for LHS operation when that is what is being done, and to also provide a version sensitive when working with 'const' object.  Once the programmer has fulfilled his part of the obligation, the compiler is the one that'll choose which one it wants to use according to the rules of C++, when working with the program code.

If the programmer uses a non-const object for a RHS operation, it's the 'non-const' overloaded version that'll be called (what's wrong with that?).  The main thing is, there will be a version available when dealing with 'const' objects for its own RHS operation; the 'non-const' version is NOT going to be the one called.
0
 
LVL 9

Expert Comment

by:jasonclarke
Comment Utility
> If the programmer uses a non-const object for a RHS operation, it's the
> 'non-const' overloaded version that'll be called (what's wrong with that?).  

Its important in something like the reference counted string class, for example, a possible partial implementation could look like:

class String {
// The const version is simple
const char& operator[](size_t i) const
{
   return mStringRep->str[i];
}

// The non-const version is not...
char& operator[](size_t i)
{
   if (mStringRep->refcount > 1)
   {
       --mStringRep->refcount;
       mStringRep = new StringRep(mStringRep->str);
   }
   return mStringRep[i];
}

//...
};

can you see that in this case just because your non-const string is being used as an r-value, you wouldn't want all that extra work to be done - its not required - and is memory and speed inefficient. If your access to the string is read-only, you don't need to bother doing the copy on write stuff - but non-const operator[] does not know how it is being called.

The way around this is to use something like Scott Meyers proxy classes.    


 
0
 
LVL 3

Expert Comment

by:Try
Comment Utility
Let us agree on certain basic points.

When 'operator[]' is being overloaded, it must be able to appear on either the LHS or the RHS in an assignment statement.  To accomplish this, it must return a reference.  However, because of the rule of C++, it cannot appear on the LHS if it's dealing with a constant object, hence a second version of overloading the '[]' operator must be provided; a version that will guarantee it can only be used as a RHS expression (so long as you don't violate any of the C++ rules and don't purposely go out of your way to show how well you can thwart the rules).

That's it!!  C++ has stated its rules.

Now, if you were to say, "Hold on!  That rule will impede me from being more effecient in my usage of processing time and memory."  That's a different story, because this is where your creativity comes in to see how well you can address those points, without violating any of C++ rules.  But then you're getting into a whole different area.  You are now getting into an area where optimization is your primary concern, and that is not what "mackayd" originally asked about.  He just wanted to know how he should go about defining 'operator[]' so that the compiler will automatically use the correct version when dealing with a LHS expression from when dealing with a RHS expression.

Your point about the reference counting is well taken, but perhaps (in this instance), reference counting is not what "mackayd" will be dealing with.
0
 

Author Comment

by:mackayd
Comment Utility
Thanks for all of the comments.

Firstly, could someone pelase give me the full reference to the "Scott Meyers" book that has been mentioned: I think that the 'proxy class' approach may be what I'm after, given the discussion so far.

What I have is an associative array which I want to access using the 'operator[]' semantics. If I reference an element that does not exist by a LHS expression (eg Fred['NewValue'] = ...) then I want "Fred" to be extended to have a new element called 'NewValue'. (If an element called 'NewValue' already exists, then this will be replaced in this case).

However, if I reference a non-existant entry in the array from the RHS, then I want to throw an exception:

Fred['OldValue'] = 123;
AllOk = Fred['OldValue'];
// AllOk should now equal 123
NotOk = Fred['BadValue'];
// Should throw an exception as 'BadValue' doesn't exist yet

As you can see, I'm not dealing with a "const" and a "non-const" object to base the selection on. Therefore, if I understand you all correctly, I must use the proxy-class approach.

Thanks

Don

PS - when you reply, could someone please answer rather than comment so that I can give them the points.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 9

Accepted Solution

by:
jasonclarke earned 100 total points
Comment Utility
Here is an example of what you need that uses the Proxy class apprroach:

#include <vector>
#include <string>

class AssocArray
{
public:

    typedef std::pair<std::string, int> arrayElement;

    class ElementProxy {
    public:
        ElementProxy(std::vector<arrayElement>& elements, const std::string& name)
            : mElements(elements), mName(name)
        {}
        ElementProxy& operator=(int value)
        {
            for (size_t i=0; i<mElements.size(); i++)
            {
                if (mElements[i].first == mName)
                {
                    mElements[i].second = value;
                    return *this;
                }
            }
            mElements.push_back(arrayElement(mName, value));
            return *this;
        }

        operator int()
        {
            for (size_t i=0; i<mElements.size(); i++)
            {
                if (mElements[i].first == mName)
                {
                    return mElements[i].second;
                }
            }
            throw "Not Found!";
            return 0;
        }
    private:
        std::vector<arrayElement>& mElements;
        std::string                mName;
    };

    ElementProxy operator[](const std::string& str)
    {
        return ElementProxy(mElements, str);
    }
private:
    std::vector<arrayElement>  mElements;
};

void main()
{
    AssocArray a;

    a["a"] = 2;

    int i = a["a"];
    int j = a["b"];
}

Hopefully this is enough to demonstrate the proxy approach.

Look here: http://cseng.awl.com/bookdetail.qry?ISBN=0-201-63371-X&ptype=0 for information about More Effective C++.

I strongly recommend both this book and More Effective C++ if you haven't seen them.  Proxy classes are described in item 30 of More Effective C++.
0
 
LVL 9

Expert Comment

by:jasonclarke
Comment Utility
Should have said both 'Effective C++' and 'More Effective C++' in the previous answer.
0
 
LVL 3

Expert Comment

by:Try
Comment Utility
STL might be what you want.  It will automatically grow a 'new entry' in an array for you (that you didn't have before) if that's what you need.

"jasonclarke's" example includes a sample use of the STL vector container together with its 'push_back()' algorithm.

While I am not discounting "jasonclarke's" offer of a solution, it is somewhat advanced in that it encompasses two different techniques usually reserved for advanced programmers:  STL and Proxy Classes.

For example, on page 223 of Scott Meyers book, "More Effective C++" where he (Meyers) is talking about Proxy Classes, he also list the 'Limitations' for using Proxy Classes as a "technique not without its drawbacks, ... because objects are used as lvalues in contexts other than just assignment, and using proxies in such contexts usually yields different behavior than using real objects."  (IOW, not always what you see, is what you get.)

"jasonclarke" is a good programmer and he has done enough work to merit the points.  I just feel you will need to ask more questions before finally feeling comfortable with an answer.
0
 

Author Comment

by:mackayd
Comment Utility
Thanks to "jasonclarke" and all of the others - I'll look at the code and try to get hold of the recommended books.

0
 
LVL 9

Expert Comment

by:jasonclarke
Comment Utility
My use of the STL was only to facilitate the demonstration of the Proxy class technique - it seemed the simplest way to get what was required.

If the STL was being used, then the simplest thing would be to avoid using your own class and use the STL map which behaves similarly anyway.

I agree that this technique is not perfect,  and it means you may have to think quite carefully when writing code that uses the classes to check that the right thing is happening.
0
 
LVL 3

Expert Comment

by:Try
Comment Utility
My use of the STL was only to facilitate the demonstration of the Proxy class technique - it seemed the simplest way to get what was required.

-----------------------------------

Not true!  Not true at all!  I will differ with you strongly here "jasonclarke".

The primary purpose of using a Proxy Class is to hide implementation details of a class to prevent access to proprietary information (including 'private' data) and proprietary logic in a class.  Moreover, it DOES NOT need the use of the STL to demonstrate the technique.

Here is a sample class whose data I am most desirous of hiding (in this class I could also include functions I may definitely wish to hide from others).

This is the sample class (hidden away some place in a separate file and not shown):


class  Implementation
{
 public:
    Implementation(int v)
    {  value = v;  }
    void setValue(int v)
    {  value = v;  }
    int getValue()  const
    {  return value;  }

 private:
    int  value;   // this is my proprietary data
};

-------------------------------

Here is my Proxy Class (preceded by Implementation class I am using for forward declaration purpose only):

class  Implementation;   // forward declaration purpose

class  Interface  // proxy class
{
 public:
    Interface(int);
    void setValue(int);
    int getValue()  const;

 private:
    Implementation *ptr;   // reason for the forward declaration
};

-----------------------------------

#include "interface.h"
#include "implementation.h"

Interface::Interface(int v) : ptr(new Implementation(v))
{  }

// call Implementation's setValue function.
// IOW, calling that which is hidden from sight

void  Interface::setValue(int v)
{  ptr->setValue(v);  }

// call Implementation's setValue function
// Likewise, calling that which is hidden from sight

int  Interface::getValue()  const
{  return ptr->getValue();  }

---------------------------------

// Hiding a class's private data with a proxy class

#include <iostream.h>
#include "interface.h"

int main()
{
   Interface  i(5);
   cout << "Interface contains: " << i.getValue()
        << " before setValue" << endl;

   i.setValue(10);
   cout << "Interface contains: " << i.getValue()
        << " after setValue" << endl;

   return 0;
}

----------------------------------

There is my Proxy Class, without the use of STL.


I'm a little confused about something, however.  First you claimed that overloading the "[]" operator twice (once for 'const' objects, and once for 'non-const' object) was inefficient due to situation such as reference counting.  Then, here you end up overloading it once, and in the process, using some rather complicated techniques that are very advanced so that persons not as technical as you or I, would find it difficult to grasp.

Then throughout your sample you qualified every occurrence of an STL container with the "std" qualifier, when a simple "using namespace std" following your "#include" statements would have been just as effective and much simpler.

Why do you feel you have to be overly complicated in order to show erudition?

===============================


Thanks to "jasonclarke" and all of the others

----------------------------

I don't see anybody else other than "jasonclarke" and myself.
0
 
LVL 9

Expert Comment

by:jasonclarke
Comment Utility
Clearly the proxy technique can be simply demonstrated without the use of the STL,  however...

I was trying to show how the Proxy technique could be used to implement an associative array.

And as such, the STL vector seemed a simple way implement a dynamic array of objects that could be used in an associative array,  that was the whole reason for using the STL stuff.

I didn't overload the const version of operator[] because it would be unused in my example, and hence I thought possibly confusing. The implementation would be the same as for operator int in the proxy, however, and it could return int rather than a proxy.

I never use the 'using' directive in a header file, because it seems unnecessary to inflict the namespaces I choose to use on users of my classes if they don't wan't to use them explicitly.

And, while your example does demonstrate a use of the Proxy technique,  I think its a different variant from the one that is  required here.  The question clearly wants the kind of transparent proxy (i.e. its presence is transparent to users of the class) that my example demonstrates.

And finally, though this is really a matter of opinion, I believe that the STL is now a fundamental part of the language,  so objecting to its use would be a bit like objecting to the use of classes or the 'if' keyword.
0
 
LVL 3

Expert Comment

by:Try
Comment Utility
The STL was not included in the Standard until mid 1994.  IOW, it has only been six years since its acceptance, and about two years since compilers began widespread incorporation of it.  IOW, it is still very new!

OTOH, classes and the 'if' keywords have been around for the better of some fifteen years, and if you were to go back further, the 'if' keyword has been around since the days of C, some twenty years or so.

How can you compare parts of the language when there are massive dissimilarities?
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to pass data into a function in C++. This is one step further in using functions. Instead of only printing text onto the console, the function will be able to perform calculations with argumentents given by the user.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now