473,378 Members | 1,321 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,378 software developers and data experts.

operator[] and different behaviour for reading and writing

A while ago I posted a question about how to get operator[] behave
differently for reading and writing. I basically wanted to make a
vector that can be queried about whether it is modified recently or
not. My first idea, using the const and non-const versions of
operator[], was clearly not correct, as was pointed out. Julián Albo
suggested I could use proxies to do that. I've done some googling for
proxies (also in this group) and personally, I think this issue should
go into the FAQ. It seems to have come up quite a few times (sometimes
in slightly different contexts) and the subject is complex enough to
warrant an easily found description of possible solutions and caveats.
Anyway, I came up with this implementation of a vector that counts the
changes made:

#include <vector>

class ChangeCountingVector {
public:
// constructor:
ChangeCountingVector(const int n=1): itsVector(n), itsChangeCounter(0)

{}
// return size:
unsigned int size() const { return itsVector.size(); }
// return number of changes since initialization:
unsigned long changes() const { return itsChangeCounter; }

// Proxy subclass for index operator with different read/write
behaviour:
class Proxy {
public:
// convert to double operator:
operator double() const { return itsCCVec.get(itsIndex); }
// assign from double:
const Proxy& operator=(const double newVal) const {
itsCCVec.put(itsIndex, newVal);
return *this;
}
// assign from a different proxy (so a[i] = b[i] works).
// Can't rely on the compiler generated default here:
const Proxy& operator=(const Proxy& p) const {
itsCCVec.put(itsIndex, p.itsCCVec.get(p.itsIndex));
return *this;
}

private:
// Constructor is private to prevent instantiating Proxy
// objects by other classes:
Proxy(ChangeCountingVector& theVec, int theIndex):
itsCCVec(theVec), itsIndex(theIndex) {}

// Owning class should be friend to be the only one allowed to
// instantiate Proxy objects:
friend class ChangeCountingVector;

// operator& is private so address can't be taken of a proxy object:
double* operator&() const ;

// reference to the vector:
ChangeCountingVector& itsCCVec;
// the index represented by the proxy:
int itsIndex;
};

const double operator[](const unsigned index) const {
return get(index);
}
Proxy operator[](const unsigned index) {
return Proxy(*this,index);
}
double get(const int index) const {
return itsVector[index];
}
double put(const int index, const double newVal) {
itsVector[index] = newVal;
++itsChangeCounter;
}

private:
std::vector<double> itsVector;
unsigned long itsChangeCounter;
};

Some benchmarking showed that this is fairly efficient. I used a
reference to the main vector in the Proxy class, instead of a pointer,
since it seemed to be slightly more efficient. I suspect that the
compiler can do a few more optimizations in that case. When all
compiler optimizations were used, g++ produced code that was comparable
in performance to a naive implementation, i.e. incrementing the counter
in the non-const operator[], regardless of whether it was writing or
only reading. Of course, this naive implementation will give incorrect
results - on reading, the vector will report being changed, but as a
performance comparison it served well enough.

On a side note: Obviously, incrementing the change counter on accessing
every single element is not the most efficient. In reality one would
add methods/operators to manipulate the vector as a whole, and count
that as one update. That's more efficient for processor optimizations
as well. Also, the Proxy class could use +=, -=, *= and /= operators
too.

My question: in most of the posts that I have found on Google
newsgroups and other sources on the internet in general, I have not
seen the "const Proxy& operator=(const Proxy& p) const" operator, just
an assignment operator that takes a double as an argument (or a
reference to a template object). But without this operator, the
compiler reports that it can't use the default assignment constructor
because of the presence of a non-static reference member. Can someone
please confirm that the code for this class is correct and that I'm not
doing anything potentially harmful in my reference juggling? Any
improvements that should be made? Thanks in advance,

regards Mark

Jul 23 '05 #1
4 2056
Mark Stijnman wrote:
A while ago I posted a question about how to get operator[] behave
differently for reading and writing. I basically wanted to make a
vector that can be queried about whether it is modified recently or
not. My first idea, using the const and non-const versions of
operator[], was clearly not correct, as was pointed out. Julián Albo
suggested I could use proxies to do that. I've done some googling for
proxies (also in this group) and personally, I think this issue should
go into the FAQ. It seems to have come up quite a few times (sometimes
in slightly different contexts) and the subject is complex enough to
warrant an easily found description of possible solutions and caveats.
Anyway, [...]


I have no opinions on your implementation or possible improvements, sorry,
but I have an opinion on a related topic you mentioned. I don't think
that this should go in the FAQ simply because this particular issue while
has come up several times in the past, is most certainly not *frequent*.
I am yet to encounter such a problem in my career. Perhaps if you could
explain what the use of your change counter is, I might see it differently
but at this point, I don't see it.

That said, you are definitely free to contact Marshall Cline and make your
suggestion to him about what section it would go to and in what form.

As to the

const Blah & operator=(const Blah &) const;

, think about it. How is the assignment going to work if the left-hand
side of it is constant?

V
Jul 23 '05 #2
I thought I had replied already yesterday, but it seems something on
the internet ate it - or I did something wrong, which is just as
likely. If you have already read a post very similar to this one,
please consider this one not written.

Victor Bazarov wrote:
Mark Stijnman wrote:
A while ago I posted a question about how to get operator[] behave
differently for reading and writing. I basically wanted to make a
vector that can be queried about whether it is modified recently or
not. My first idea, using the const and non-const versions of
operator[], was clearly not correct, as was pointed out. Julián Albo suggested I could use proxies to do that. I've done some googling for proxies (also in this group) and personally, I think this issue should go into the FAQ. It seems to have come up quite a few times (sometimes in slightly different contexts) and the subject is complex enough to warrant an easily found description of possible solutions and caveats. Anyway, [...]
I have no opinions on your implementation or possible improvements,

sorry, but I have an opinion on a related topic you mentioned. I don't think that this should go in the FAQ simply because this particular issue while has come up several times in the past, is most certainly not *frequent*. I am yet to encounter such a problem in my career. Perhaps if you could explain what the use of your change counter is, I might see it differently but at this point, I don't see it.
The change counter is just an example of a vector that can give
information to clients as to whether it has changed or not. It could
just as well have been a simple "dirty" flag, or even a "Notify" call
to one or more Observers. In my case, I want to perform interpolation
on a vector of data and I want to cache the interpolation data
structures. It takes O(N) operations to regenerate those data, so one
does not want to do that for every call to the interpolate function.
Only when the data has actually changed should the interpolation data
structures be regenerated.

There are also other cases where people have wanted to have different
behaviour depending on wheter operator[] was used for assigning or just
reading. I have seen several people who wanted a sparse vector class,
that only stores non-zero elements. Reading from a position where no
non-zero element is defined should return 0. Writing to such a position
should however insert a new non-zero element into the sparse vector.

Granted, you can always use 'get' and 'set' (or 'read' and 'write')
members, and a lot of people seem to think you always should, but a lot
of other people (including me) like the [] form, since it makes code
look more intuitive.
That said, you are definitely free to contact Marshall Cline and make your suggestion to him about what section it would go to and in what form.

As to the

const Blah & operator=(const Blah &) const;

, think about it. How is the assignment going to work if the left-hand side of it is constant?

V


The Proxy will not change, only the reference it represents changes. So
the Proxy object can be declared const. It looks counter-intuitive, but
it works.

regards Mark

Jul 23 '05 #3
Mark Stijnman wrote:
I thought I had replied already yesterday, but it seems something on
the internet ate it - or I did something wrong, which is just as
likely. If you have already read a post very similar to this one,
please consider this one not written.
I haven't.
[...]
The change counter is just an example of a vector that can give
information to clients as to whether it has changed or not. It could
just as well have been a simple "dirty" flag, or even a "Notify" call
to one or more Observers. In my case, I want to perform interpolation
on a vector of data and I want to cache the interpolation data
structures. It takes O(N) operations to regenerate those data, so one
does not want to do that for every call to the interpolate function.
Only when the data has actually changed should the interpolation data
structures be regenerated.
Yet another reason to make "dirty" flag to be settable from outside.
There are also other cases where people have wanted to have different
behaviour depending on wheter operator[] was used for assigning or just
reading. I have seen several people who wanted a sparse vector class,
that only stores non-zero elements. Reading from a position where no
non-zero element is defined should return 0. Writing to such a position
should however insert a new non-zero element into the sparse vector.
I am unable to come up with a solution (so far) when a bit more complex
mechanism is used than a simple assignment operator. See below.
Granted, you can always use 'get' and 'set' (or 'read' and 'write')
members, and a lot of people seem to think you always should, but a lot
of other people (including me) like the [] form, since it makes code
look more intuitive.
Really? *More* intuitive? Compare the behaviour of that operator for
std::map. Notice how behaviour of it is *not* different depending on
which side of the assignment operator it's on. *Not* different. And
it's not because it's impossible to implement. It's because it is
counter-intuitive, again.

Think about how you should implement this (and how much more intuitive it
is) when you have this situation:

void set_arg_to_three(double& arg) {
arg = 3;
}
...
yourspecialvector<double> vd(100);
set_arg_to_three(vd[99]); // I want vd[99] to now be 3

How is your proxy going to help you here?

Yes, the example *seems* contrived. Why don't I simply use

vd[99] = some_way_to_get_my_three_here();

notation, right? And, yes, if I could, I probably would. But if I am
stuck using a third-party library? I am then forced to write something
like

{ double temp;
set_arg_to_three(temp);
vd[99] = temp; }

or

inline double set_arg_to_three_adapter() // my new wrapper function
{
double temp;
set_arg_to_three(temp);
return temp;
}
...
vd[99] = set_arg_to_three_adapter();

both are really pushing the whole intuitiveness argument.
[...about operator= for a const object...]
The Proxy will not change, only the reference it represents changes. So
the Proxy object can be declared const. It looks counter-intuitive, but
it works.


Yet another argument not to do that. If it looks counter-intuitive, it
*is* counter-intuitive.

I am not saying "don't do that". I am just giving you the reasons why I
still think that it's not a *frequently* asked question and why before you
ever initiate the process of putting it in the FAQ, you should think hard
*what* you put in the FAQ as the answer for that question.

V
Jul 23 '05 #4

Victor Bazarov wrote:
[...]
The change counter is just an example of a vector that can give
information to clients as to whether it has changed or not. It could just as well have been a simple "dirty" flag, or even a "Notify" call to one or more Observers. In my case, I want to perform interpolation on a vector of data and I want to cache the interpolation data
structures. It takes O(N) operations to regenerate those data, so one does not want to do that for every call to the interpolate function. Only when the data has actually changed should the interpolation data structures be regenerated.
Yet another reason to make "dirty" flag to be settable from outside.


Sorry, I miss what reason you are referring to.
There are also other cases where people have wanted to have different behaviour depending on wheter operator[] was used for assigning or just reading. I have seen several people who wanted a sparse vector class, that only stores non-zero elements. Reading from a position where no non-zero element is defined should return 0. Writing to such a position should however insert a new non-zero element into the sparse vector.
I am unable to come up with a solution (so far) when a bit more complex mechanism is used than a simple assignment operator. See below.
Granted, you can always use 'get' and 'set' (or 'read' and 'write')
members, and a lot of people seem to think you always should, but a
lot of other people (including me) like the [] form, since it makes code look more intuitive.


Really? *More* intuitive? Compare the behaviour of that operator

for std::map. Notice how behaviour of it is *not* different depending on
which side of the assignment operator it's on. *Not* different. And
it's not because it's impossible to implement. It's because it is
counter-intuitive, again.
With 'intuitive' I mean that for a lot of objects like vectors, it's
intuitive to use index operators to access the elements. Of course the
implementation of the index operators themselves should be intuitive. I
would like to have an operator[] to not modify the visible state of the
object it is called on, when it is only used as a rvlaue. In this
respect, the operator[] on map is indeed not intuitive. When the
implementation is not intuitive, it should probably be avoided.
Think about how you should implement this (and how much more intuitive it is) when you have this situation:

void set_arg_to_three(double& arg) {
arg = 3;
}
...
yourspecialvector<double> vd(100);
set_arg_to_three(vd[99]); // I want vd[99] to now be 3

How is your proxy going to help you here?

I see your point, and it is clearly a weakness of this approach. I also
would not know a way around this - it's solution would be highly
non-trivial.

Unfortunately, your example would also not work using the set and get
methods provided. Only a 'naked reference' to the data would work
there, after which the flag or change counter should be updated
manually. Unfortunately, that will break encapsulation, in that it
allows one to change the data, without updating the change counter.
[...about operator= for a const object...]
The Proxy will not change, only the reference it represents changes. So the Proxy object can be declared const. It looks counter-intuitive, but it works.
Yet another argument not to do that. If it looks counter-intuitive,

it *is* counter-intuitive.

I am not saying "don't do that". I am just giving you the reasons why I still think that it's not a *frequently* asked question and why before you ever initiate the process of putting it in the FAQ, you should think hard *what* you put in the FAQ as the answer for that question.

V


I agree, which is why I put it up for discussion. And even a "you
can't" or "you can, but with such-and-such limitations" can already be
helpful as an answer to a FAQ.

Jul 23 '05 #5

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

45
by: Jordan Rastrick | last post by:
Can anybody please give me a decent justification for this: class A(object): def __init__(self, a): self.a = a def __eq__(self, other): return self.a == other.a s = A(3)
137
by: Philippe C. Martin | last post by:
I apologize in advance for launching this post but I might get enlightment somehow (PS: I am _very_ agnostic ;-). - 1) I do not consider my intelligence/education above average - 2) I am very...
30
by: | last post by:
I have not posted to comp.lang.c++ (or comp.lang.c++.moderated) before. In general when I have a C++ question I look for answers in "The C++ Programming Language, Third Edition" by Stroustrup....
20
by: Patrick Guio | last post by:
Dear all, I have some problem with insertion operator together with namespace. I have a header file foo.h containing declaration of classes, typedefs and insertion operators for the typedefs in...
19
by: scroopy | last post by:
Is it impossible in C++ to create an assignment operator for classes with const data? I want to do something like this class MyClass { const int m_iValue; public: MyClass(int...
34
by: Chris | last post by:
Is there ever a reason to declare this as if(*this == rhs) as opposed to what I normally see if(this == &rhs) ?
5
by: simudream | last post by:
//hi maybe helpful others can look at this code and //tell me why the class code won't behave like //intrinsic types with post and pre increment //Version: 1.00 #include <iostream> using...
4
by: subramanian100in | last post by:
In the book, C++ Coding Standards book by Hereb Sutter and Andrei Alexandrescu, in Item 40 on pages 86-87 viz, "Avoid providing implicit conversions", the authors have advised the use of named...
19
by: C++Liliput | last post by:
I have a custom String class that contains an embedded char* member. The copy constructor, assignment operator etc. are all correctly defined. I need to create a map of my string (say a class...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.