Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Overloading the [] operator for read/write...

68 views
Skip to first unread message

TDH1978

unread,
Apr 26, 2021, 9:10:56 PM4/26/21
to
I have a database-related class (MyDB) where I would like to overload
the [] operator, so that the class looks and behaves like a map.

MyDB mydb; // key-value pairs, like a map

//
// read from database
//
X x = mydb[y];

//
// write to database (using proxy object)
//
mydb[y] = x;


Does anyone know how I can do this?

I tried using a proxy class for the 'write' operation, but when it came
time to print a database entry, the compiler would complain that it
does not know how to print the proxy object, when in fact it should be
printing 'x' from the 'read' operation:

cout << mydb[y] << endl; // compiler error; tries to print proxy
object and not 'x'.


I was hoping that the last 'cout' statement would be the same as:

cout << x << endl;

But the compiler had other ideas.

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

Below is some pseudo-code (does not compile due to incompleteness).

//
// Proxy for the MyDB class
//
class MyDBProxy_
{
public:

MyDBProxy_(MyDB* owner) : mydb_(owner) {}

void set_key(const Y& y)
{
y_ = y;
}

void operator=(const X& x)
{
mydb_->store_value_(y_, x); // internal database operation
}

private:
MyDB* mydb_;
Y y_;
};


//
// The actual database class
//
class MyDB
{
friend class MyDBProxy_;

public:

MyDB() : proxy_(this)
{
// internal databse construction
}

//
// read operation: X x = mydb[y];
//
X operator[](const Y& y) const
{
return fetch_value_(y); // internal database operation
}

//
// write operation: mydb[y] = x;
//
MyDBProxy_& operator[](const Y& y)
{
proxy_.set_key(y);

return proxy_;
}

// definition of fetch_value_ here...
// definition of store_value_ here...

private:

MyDBProxy_ proxy_; // proxy object
};


Paavo Helde

unread,
Apr 27, 2021, 7:02:17 AM4/27/21
to
27.04.2021 04:10 TDH1978 kirjutas:
> I have a database-related class (MyDB) where I would like to overload
> the [] operator, so that the class looks and behaves like a map.
>
>   MyDB mydb;  // key-value pairs, like a map
>
>   //
>   // read from database
>   //
>   X x = mydb[y];
>
>   //
>   // write to database (using proxy object)
>   //
>   mydb[y] = x;
>
>
> Does anyone know how I can do this?
>
> I tried using a proxy class for the 'write' operation, but when it came
> time to print a database entry, the compiler would complain that it does
> not know how to print the proxy object, when in fact it should be
> printing 'x' from the 'read' operation:
>
>   cout << mydb[y] << endl;  // compiler error; tries to print proxy
> object and not 'x'.

You need to add a streaming operator for your proxy. Something like:

std::ostream& operator<<(std::ostream& os, const Proxy& proxy) {
os << proxy.get_value();
return os;
}

TDH1978

unread,
Apr 30, 2021, 5:08:16 PM4/30/21
to
On 2021-04-27 11:02:01 +0000, Paavo Helde said:

> You need to add a streaming operator for your proxy. Something like:
>
> std::ostream& operator<<(std::ostream& os, const Proxy& proxy) {
> os << proxy.get_value();
> return os;
> }

Thank you; I'm sure that would work, but I would have expected
operator<<() to prefer the 'const':

X operator[](const Y& y) const

over the 'non-const':

MyDBProxy_& operator[](const Y& y)


Do you know why the compiler is choosing one over the other?

What if, instead of operator<<(), I had a function:

void foo(const X& x);

and I pass it:

foo(mydb[y]);

Would the compiler complain because it thinks mydb[y] returns the proxy
and not x?

Paavo Helde

unread,
May 2, 2021, 3:15:26 PM5/2/21
to
01.05.2021 00:08 TDH1978 kirjutas:
> On 2021-04-27 11:02:01 +0000, Paavo Helde said:
>
>> You need to add a streaming operator for your proxy. Something like:
>>
>> std::ostream& operator<<(std::ostream& os, const Proxy& proxy) {
>>     os << proxy.get_value();
>>     return os;
>> }
>
> Thank you; I'm sure that would work, but I would have expected
> operator<<() to prefer the 'const':
>
>   X operator[](const Y& y) const
>
> over the 'non-const':
>
>   MyDBProxy_& operator[](const Y& y)
>

Which operator<<()? I your example there was none, and there is no default.

I guess you mean the line:

> cout << mydb[y] << endl; // compiler error; tries to print proxy
object and not 'x'.

Here, operator[] is called before passing the result to any operator<<,
existing or not. IOW, the meaning of 'mydb[y]' does not change depending
on the expression where it appears in.

Whether a const or non-const overload is preferred, is determined simply
by if 'mydb' is const or non-const. You can always cast to const if
that's what you need:

const MyDB& cmydb = mydb;
cout << cmydb[y] << endl

> Do you know why the compiler is choosing one over the other?
>
> What if, instead of operator<<(), I had a function:
>
>   void foo(const X& x);
>
> and I pass it:
>
>   foo(mydb[y]);
>
> Would the compiler complain because it thinks mydb[y] returns the proxy
> and not x?

Why don't you try out and see it by yourself what happens? Such
experiments are a must if you want to understand how a language works.

Bonita Montero

unread,
May 3, 2021, 3:27:40 AM5/3/21
to
> You need to add a streaming operator for your proxy. Something like:
^^^^^^^^^
>
> std::ostream& operator<<(std::ostream& os, const Proxy& proxy) {
>     os << proxy.get_value();
>     return os;
> }

Better:
template<typename T, typename Traits>
std::basic_ostream<T, Traits> &operator <<( std::basic_ostream<T,
Traits> &os, Proxy const &p )
{
os << p.get_value();
return os;
}

daniel...@gmail.com

unread,
May 3, 2021, 10:57:56 AM5/3/21
to
On Monday, April 26, 2021 at 9:10:56 PM UTC-4, TDH1978 wrote:
> I have a database-related class (MyDB) where I would like to overload
> the [] operator, so that the class looks and behaves like a map.
>
> MyDB mydb; // key-value pairs, like a map
>
> //
> // read from database
> //
> X x = mydb[y];
>
> //
> // write to database (using proxy object)
> //
> mydb[y] = x;
>
> Does anyone know how I can do this?
>
> I tried using a proxy class for the 'write' operation

> // Proxy for the MyDB class
> //
> class MyDBProxy_
> {

As C++ is currently specified, proxy abstractions produce surprising
behavior when combined with auto, e.g.

auto x = mydb[y];

would return a value of type MyDBProxy_, not X. There is a proposal
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0672r0.pdf)
to address that issue, but it doesn't seem to have much traction in the
ISO C++ committee. I would therefore suggest to just use load (or get) and
save (or set) functions instead. For your use case, the notational convenience
of operator[] doesn't compensate for the additional complexity and
incompatibility with auto.

Daniel

Paavo Helde

unread,
May 3, 2021, 12:14:03 PM5/3/21
to
03.05.2021 17:57 daniel...@gmail.com kirjutas:

> As C++ is currently specified, proxy abstractions produce surprising
> behavior when combined with auto, e.g.
>
> auto x = mydb[y];
>
> would return a value of type MyDBProxy_, not X. [...]
> For your use case, the notational convenience
> of operator[] doesn't compensate for the additional complexity and
> incompatibility with auto.

As a relatively new feature, I'm afraid auto tends to be overused
currently in some places. Seeing the actual type of the variable in code
is a huge benefit for the reader. Using 'auto' is only justified if the
actual type is both verbose and either obvious or not important. In the
OP-s example:

X x = mydb[y];

(this would work fine when there is a proper conversion operator defined
from proxy to X).

Here, X is a single class name, so it ought to be not too verbose even
when (hopefully) replaced with a bit longer and more descriptive name in
real code. This type is also most probably important and it might be not
obvious for a casual reader who is not familiar with what exactly mydb
is and does.

So there is no real reason to use auto here, IMO. I can understand why
the proposal for getting auto working here would not get much traction
in the committee, it looks more like a great feature for obfuscated C++
contests.

daniel...@gmail.com

unread,
May 3, 2021, 2:33:18 PM5/3/21
to
On Monday, May 3, 2021 at 12:14:03 PM UTC-4, Paavo Helde wrote:
> 03.05.2021 17:57 daniel...@gmail.com kirjutas:
>
> > As C++ is currently specified, proxy abstractions produce surprising
> > behavior when combined with auto, e.g.
> >
> > auto x = mydb[y];
> >
> > would return a value of type MyDBProxy_, not X. [...]
> > For your use case, the notational convenience
> > of operator[] doesn't compensate for the additional complexity and
> > incompatibility with auto.
> As a relatively new feature, I'm afraid auto tends to be overused
> currently in some places. Seeing the actual type of the variable in code
> is a huge benefit for the reader. Using 'auto' is only justified if the
> actual type is both verbose and either obvious or not important.

That's one view. Another is to use auto liberally, which is a view
that has been expressed in this newsgroup, on stack overflow, and
elsewhere. Regardless of the validity of the arguments, the author
of a library will likely find that some users use auto a lot, and should
be cautious about introducing the proxy abstraction where it is merely
sugar.

Daniel

David Brown

unread,
May 4, 2021, 12:34:51 PM5/4/21
to
I would go further and say that you definitely /want/ auto to give the
proxy object, not the conversion, in a case like this. When
initialising from "mydb[y]", sometimes you would want the converted
value, and sometimes the proxy object - and when you want the converted
value you can, as you say, write "X x = mydb[y];". If you want the
proxy itself then it would be a good use of "auto" - the type is likely
to be verbose and its details would be a clutter in the code.

0 new messages