Multiple operator overloading

A

Andrea Crotti

Looking on the internet looks like it's perfectly fine to overload
operators, but why then this doesn't work

--8<---------------cut here---------------start------------->8---
class Myclass
{
public:
int operator[](int index) { return 0; }
string operator[](int index) { string s = "ciao"; return s; }
};
--8<---------------cut here---------------end--------------->8---

and gives the error
--8<---------------cut here---------------start------------->8---
try.cpp:23: error: ‘std::string Myclass::eek:perator[](int)’ cannot be overloaded
try.cpp:22: error: with ‘int Myclass::eek:perator[](int)’
--8<---------------cut here---------------end--------------->8---

It would be very useful in another case (thread Globals), how otherwise
I can achieve the same result?

The only other way coming to my mind is another class with a template
parameter and then specializing it for the different types, but it
sounds a bit too much for such a simple thing...
 
S

Saeed Amrollahi

Looking on the internet looks like it's perfectly fine to overload
operators, but why then this doesn't work

--8<---------------cut here---------------start------------->8---
class Myclass
{
public:
    int operator[](int index) { return 0; }
    string operator[](int index) { string s = "ciao"; return s; }};

--8<---------------cut here---------------end--------------->8---

and gives the error
--8<---------------cut here---------------start------------->8---
try.cpp:23: error: ‘std::string Myclass::eek:perator[](int)’ cannot be overloaded
try.cpp:22: error: with ‘int Myclass::eek:perator[](int)’
--8<---------------cut here---------------end--------------->8---

It would be very useful in another case (thread Globals), how otherwise
I can achieve the same result?

The only other way coming to my mind is another class with a template
parameter and then specializing it for the different types, but it
sounds a bit too much for such a simple thing...

Hi Andrea
You can't overload two functions that are different just in return
type. It is
a general concept in "Function-name overloading":
void f(int);
int f(int); // error: only they are different in return types
Same applies to overloaded operators.

Regards,
-- Saeed Amrollahi
 
A

Andrea Crotti

Saeed Amrollahi said:
Hi Andrea
You can't overload two functions that are different just in return
type. It is
a general concept in "Function-name overloading":
void f(int);
int f(int); // error: only they are different in return types
Same applies to overloaded operators.

Regards,
-- Saeed Amrollahi

Ah true now I got it...
It would have been too easy of course, other ways to get the same target?
 
A

Andrea Crotti

I then had another idea, create the template class that returns to the
correct type, and then specialize it correctly.

--8<---------------cut here---------------start------------->8---

template<typename T>
class Attribute
{
protected:
string value;
public:
void setValue(string& _value) { value = _value; }
virtual T fromString(const string&) = 0;
};

class IntAttr : public Attribute<int>
{
public:
int fromString(const string& value) {
return atoi(value.c_str());
}
};

class Globals
{
private:
ConfigParser parser;
// maybe the key should be (char *) instead
std::map<string, Attribute> config;

public:
Globals();
Attribute& operator[](const string& idx) { return config[idx]; }
};
--8<---------------cut here---------------end--------------->8---

I thought it was working but of course there's a problem, since
"Attribute" it's not a type, so I can't make the generic map...
Looks like I'm in an empasse and there's no way to do what I wanted,
it's really a pity though.
 
A

Andrea Crotti

Daniel T. said:
Andrea Crotti said:
Looking on the internet looks like it's perfectly fine to overload
operators, but why then this doesn't work

--8<---------------cut here---------------start------------->8---
class Myclass
{
public:
int operator[](int index) { return 0; }
string operator[](int index) { string s = "ciao"; return s; }
};
--8<---------------cut here---------------end--------------->8---

and gives the error
--8<---------------cut here---------------start------------->8---
try.cpp:23: error: ‘std::string Myclass::eek:perator[](int)’ cannot be overloaded
try.cpp:22: error: with ‘int Myclass::eek:perator[](int)’
--8<---------------cut here---------------end--------------->8---

It would be very useful in another case (thread Globals), how otherwise
I can achieve the same result?

What result are you trying to achieve?

I want to have a map where the keys are strings and the value can be of
different types.

So that I can do
--8<---------------cut here---------------start------------->8---
for key in mapping.keys():
mapping[key] = mapping[key].fromString(value)
--8<---------------cut here---------------end--------------->8---

so that I can declare my configuration without too much code
duplication, something like

--8<---------------cut here---------------start------------->8---
IntAttr x; // attribute representing an integer
conf["num_land"] = x;
VecStrAttr y;
....
--8<---------------cut here---------------end--------------->8---

But my impression is that it's just not possible in C++.

Another option without using the template would be to just inherit

--8<---------------cut here---------------start------------->8---
class Attribute
{
protected:
string value;
public:
void setValue(string& _value) { value = _value; }
// virtual T fromString(const string&) = 0;
};

class IntAttr : public Attribute
{
public:
int fromString(const string& value) {
return atoi(value.c_str());
}
};

class Globals
{
private:
ConfigParser parser;
// maybe the key should be (char *) instead
std::map<string, Attribute> config;

public:
Globals();
Attribute& operator[](const string& idx) {
return config[idx].fromString();
}
};
--8<---------------cut here---------------end--------------->8---

but still it doesn't work because without the template fromString()
can't be defined since I don't know the type of the return value...
 
J

James Kanze

Ah true now I got it...
It would have been too easy of course, other ways to get the same target?

The classical solution is to have the function or operator
return a proxy with overloaded conversion operators, e.g.:

class Toto
{
public:
class Proxy
{
Toto const* myOwner;
int myIndex;
public:
Proxy(Toto const* owner, int index)
: myOwner(owner)
, myIndex(index)
{
}
operator int() const { myOwner-
getInt(myIndex); }
operator std::string() const { myOwner-
getString(myIndex); }
};
Proxy operator[](int index) const
{
return Proxy(this, index);
}
// ...
};
 
A

Andrea Crotti

James Kanze said:
The classical solution is to have the function or operator
return a proxy with overloaded conversion operators, e.g.:

class Toto
{
public:
class Proxy
{
Toto const* myOwner;
int myIndex;
public:
Proxy(Toto const* owner, int index)
: myOwner(owner)
, myIndex(index)
{
}
operator int() const { myOwner-
getInt(myIndex); }
operator std::string() const { myOwner-
getString(myIndex); }
};
Proxy operator[](int index) const
{
return Proxy(this, index);
}
// ...
};

Great thanks a lot that's what I was looking for...
 
A

Andrea Crotti

James Kanze said:
Ah true now I got it...
It would have been too easy of course, other ways to get the same target?

The classical solution is to have the function or operator
return a proxy with overloaded conversion operators, e.g.:

class Toto
{
public:
class Proxy
{
Toto const* myOwner;
int myIndex;
public:
Proxy(Toto const* owner, int index)
: myOwner(owner)
, myIndex(index)
{
}
operator int() const { myOwner-
getInt(myIndex); }
operator std::string() const { myOwner-
getString(myIndex); }
};
Proxy operator[](int index) const
{
return Proxy(this, index);
}
// ...
};

Last thing, now I rewrote it for my situation:

class Attribute
{
public:
class Proxy
{
Attribute const *owner;
string value;
public:
Proxy(Attribute const* _owner, string& _value)
: owner(_owner), value(_value) {}

operator int() const {
owner->getInt(value);
}
operator std::string() const {
owner->getString(value);
}
};

Proxy operator[](string& index) const {
return Proxy(this, index);
}

int getInt(const string& value) const {
return atoi(value.c_str());
}
string getString(const string& value) const { return value; }
};


But how do I use it?
Where am I supposed to give in the values I want

I thought something like this below but it's not correct, I have to call
the overloaded int which is in the class Proxy, not Attribute.

Attribute x;
x["new"] = int(10);
 
A

Andrea Crotti

Daniel T. said:
What you are trying to do isn't a good idea. Instead, make separate
maps, one for each type. After all, the code that puts the value in the
map knows what type the value is, and the code that retrieves the value
also has to know the type of the value, so setting up separate maps
isn't a hardship.

map<string, int> intGlobals;
map<string, string> stringGlobals;

If you are desperate to make poor design decisions, you could use
boost::any.
http://www.boost.org/doc/libs/1_44_0/doc/html/any.html#id1010145

Well but actually I have (at the moment) all ints and one
vector<string>.
What can happen later is maybe to change something to long, to add
another vector<string> and so on, but frankly having a dictionary for
every type is quite annoying.

I see that it becomes very convoluted in c++ and then maybe I'll drop
the idea, but I don't see why it should be a poor design in general...
 
A

Andrea Crotti

Daniel T. said:
Your throwing away information that you will need later. That's why it
is a poor design in general. Don't throw away information that you will
need later.

Well yes sure you're right, but what I wanted to do in general was

int glob_var = GLOBALS["glob_var"];

for example, where [] returns the right type and I would have had only
one way to get the variables.

Unfortunately that's not possible so maybe it's not a good idea...

But by the way in some cases I don't see why for example the call f() to

int f() {
string f() {

would be ambiguous.
I mean, if I tell the compiler to not try any automatic type casting and
just give error if the left and right side are not exactly the same
type, imho I should not run into problem.

Even having

char f()
int f()

doing
char x = f()
and
int x = f()

should call the right thing.
Why then is not allowed?
 
S

Stuart Redmann

I disagree. Having to work with several dictionaries (one for each
type) seems a much poorer decision to me. I'd prefer the boost::any
solution (actually I use this solution, although I try to avoid
boost::any as best as I can).


Well yes sure you're right, but what I wanted to do in general was

int glob_var = GLOBALS["glob_var"];

for example, where [] returns the right type and I would have had only
one way to get the variables.

Unfortunately that's not possible so maybe it's not a good idea...


You could try this:

int glob_var = GLOBALS["glob_var"][glob_var];

Passing the variable as second parameter would give the object GLOBALS
enough information about the type that it should return. This calls
for a lot more code, so I'd still rather use boost::any.

But by the way in some cases I don't see why for example the call f() to

int f() {
string f() {

would be ambiguous.
I mean, if I tell the compiler to not try any automatic type casting and
just give error if the left and right side are not exactly the same
type, imho I should not run into problem.

Yeah, I don't see why this should not work as you suggest. Most
probably because there is next to no possibility to tell the compiler
which particalar overload you want:

// Overload resolution when type of parameter is ambigous.
void f (double);
void f (float);

int main ()
{
int i = 3;
f (static_cast<float> (i));
}

// Overload resolution when return type is ambigous.
double f (); // Does not compile of course, but
float f (); // just assume it did.

int main ()
{
int i = f (); // How to tell the compiler that we want
// the "overload" of f that returns a
// double?
}

No language I know provides such a feature that the return type is
somehow determined by the type of the variable the function call is
assigned to.

Regards,
Stuart
 
A

Andrea Crotti

Stuart Redmann said:
I disagree. Having to work with several dictionaries (one for each
type) seems a much poorer decision to me. I'd prefer the boost::any
solution (actually I use this solution, although I try to avoid
boost::any as best as I can).

Yes well I can't use boost, and I also agree is not so nice.
At this point I will not use a dictionary at all, since then at least I
have a faster access to the variables.

Of course it's not so nice but if I can't actually loop for setting them
to their correct value I don't gain anything with a dictionary.
You could try this:

int glob_var = GLOBALS["glob_var"][glob_var];

Passing the variable as second parameter would give the object GLOBALS
enough information about the type that it should return. This calls
for a lot more code, so I'd still rather use boost::any.

Now the second parameter is the value of the variable, how could that
help?
And should I implement this on the dictionary side to get something
meaningful?
Yeah, I don't see why this should not work as you suggest. Most
probably because there is next to no possibility to tell the compiler
which particalar overload you want:

// Overload resolution when type of parameter is ambigous.
void f (double);
void f (float);

int main ()
{
int i = 3;
f (static_cast<float> (i));
}

// Overload resolution when return type is ambigous.
double f (); // Does not compile of course, but
float f (); // just assume it did.

int main ()
{
int i = f (); // How to tell the compiler that we want
// the "overload" of f that returns a
// double?
}

No language I know provides such a feature that the return type is
somehow determined by the type of the variable the function call is
assigned to.

Yes of course, would be compulsory to add a constraint that the type
should be exactly the same, no automatic conversion allowed. But well
no compiler does it so there must be some good reasons... (it's a pity
because it would be useful imho).
 
J

James Kanze

James Kanze said:
The classical solution is to have the function or operator
return a proxy with overloaded conversion operators, e.g.:
class Toto
{
public:
class Proxy
{
Toto const* myOwner;
int myIndex;
public:
Proxy(Toto const* owner, int index)
: myOwner(owner)
, myIndex(index)
{
}
operator int() const { myOwner-
getInt(myIndex); }
operator std::string() const { myOwner-
getString(myIndex); }
};
Proxy operator[](int index) const
{
return Proxy(this, index);
}
// ...
};
Last thing, now I rewrote it for my situation:

class Attribute
{
public:
class Proxy
{
Attribute const *owner;
string value;
public:
Proxy(Attribute const* _owner, string& _value)
: owner(_owner), value(_value) {}

operator int() const {
owner->getInt(value);
}
operator std::string() const {
owner->getString(value);
}
};
Proxy operator[](string& index) const {
return Proxy(this, index);
}
int getInt(const string& value) const {
return atoi(value.c_str());
}
string getString(const string& value) const { return value; }
};
But how do I use it?

Like any other operator[]. Except that an additional user
defined conversion is involved (which can affect overload
resolution), it works like any other operator[].
Where am I supposed to give in the values I want
I thought something like this below but it's not correct, I have to call
the overloaded int which is in the class Proxy, not Attribute.
Attribute x;
x["new"] = int(10);

If you also want to use it for assignment, you have to add
assignment operators to the proxy, e.g.:

class Attribute
{
// ...
class Proxy
{
// ...
Proxy const& operator=(int newValue) const
{
owner->setInt(newValue);
return *this;
}
Proxy const& operator=(std::string const& newValue) const
{
owner->setString(newValue);
return *this;
}

...

(Note the use of an operator= which is const. That's a hallmark
of a proxy.)
 
J

James Kanze

"Daniel T." writes:
I disagree. Having to work with several dictionaries (one for each
type) seems a much poorer decision to me. I'd prefer the boost::any
solution (actually I use this solution, although I try to avoid
boost::any as best as I can).

It depends on the context. If your data is initially two
different types, and will always be two different types, it
makes sense to maintain the type information. Usually, however,
when I've used something like this, my data comes from (and goes
into) a configuration file, where everything is a string. The
map in the object is also string to string, and it's only when
actually reading the data that it will be converted to the
desired type (with an exception if the conversion doesn't work).

[...]
No language I know provides such a feature that the return type is
somehow determined by the type of the variable the function call is
assigned to.

I know of one: C++. But only for conversion operators. See my
example using a proxy.
 
B

Bo Persson

Andrea said:
But by the way in some cases I don't see why for example the call
f() to

int f() {
string f() {

would be ambiguous.
I mean, if I tell the compiler to not try any automatic type
casting and just give error if the left and right side are not
exactly the same type, imho I should not run into problem.

Even having

char f()
int f()

doing
char x = f()
and
int x = f()

should call the right thing.
Why then is not allowed?

That would be a special rule for overloading on return values, that
only works if the result is assigned to a variable of the same type.
That's not a requirement for C or C++ code. You could just as well do:

f();
g(f(), f());
float x = f();

What would happen here?


Bo Persson
 
N

Niklas Holsti

Stuart said:
...

int main ()
{
int i = f (); // How to tell the compiler that we want
// the "overload" of f that returns a
// double?

(Perhaps "that returns an int" was meant?)
}

No language I know provides such a feature that the return type is
somehow determined by the type of the variable the function call is
assigned to.

The Ada language does that. Overload resolution of functions in Ada
takes into account both the parameter types and the result type.

This becomes tricky when you have nested function calls where several
levels in the nest have overloads for various parameter and result
types. But it works well in practice.
 
R

red floyd

(Perhaps "that returns an int" was meant?)



The Ada language does that. Overload resolution of functions in Ada
takes into account both the parameter types and the result type.

Ada can do this because ignoring a return value is an error.
 
S

Stuart Redmann

The Ada language does that. Overload resolution of functions in Ada
takes into account both the parameter types and the result type.

This becomes tricky when you have nested function calls where several
levels in the nest have overloads for various parameter and result
types. But it works well in practice.

I didn't know that. Well, one more point for Ada. Though I'm a
professional C++ programmer I'm still amazed about the ingenuity of
the Ada compiler. It's a pity that it only caught on in niche
business.

Regards,
Stuart
 
N

Niklas Holsti

red said:
Ada can do this because ignoring a return value is an error.

I assume you mean that C++ could not make use of the return type to
resolve overloads, because then a return-value-ignoring call statement
of the form

f();

could not use the expected type of the return value from f() to choose
between overloaded f() functions.

But there are already lots of other ways to write ambiguous calls in
Ada. For example, an assignment statement y := foo(bar(x)) is ambiguous
if there are two bar()'s that take x and return two different types P
and Q, and two foo()'s that take parameters of those types, but both
return the type of y.

The Ada compiler just reports an ambiguity, and the programmer must
correct it.

Ada has a "type qualifier" expression that tells the compiler the
expected type of an expression. This takes the form T'(e) where T is a
type-name and e is an expression, possibly involving overloaded
functions. Writing

y := foo(P'(bar(x)));

forces the use of the bar() that returns type P and of the foo() that
takes type P, and

y := foo(Q'(bar(x)));

forces the use of the bar() that returns type Q and of the foo() that
takes type Q.

So Ada could allow function calls as statements, and in case of
ambiguity you would write

P'(bar(x));

to call the P-returning bar() and ignore its return value, and vice
versa for Q.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top