Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Ambiguity by making overloaded operator function-const - why?

Reply
Thread Tools

Ambiguity by making overloaded operator function-const - why?

 
 
abendstund@gmail.com
Guest
Posts: n/a
 
      06-01-2008
Hi,


I have the following code and trouble with ambiguity due to operator
overloading..

The code is also at http://paste.nn-d.de/441

snip>>

#include <iostream>
#include <string>
#include <map>

using namespace std;


class ConfigItem;
typedef map<wstring, ConfigItem> ConfigMap;

class ConfigItem {

public:
ConfigItem() { type=NONE; s[0]=0; }


ConfigItem(const wchar_t *str) {
type=STRING;
wcscpy(s, str);
}
operator const wchar_t*() const {
return s;
}
wchar_t operator[](int pos) const {
return (operator const wchar_t*())[pos];
}


ConfigItem& operator[](const wchar_t *option) {
return operator[](wstring(option));
}
ConfigItem& operator[](const wstring &option) {
switch (type) {
case MAP: return (*cm)[option];
default: return *this;
}
}

private:
enum {
NONE,
INT,
STRING,
MAP,
} type;

wchar_t s[512];
ConfigMap *cm;
};


int main() {
if (wchar_t(ConfigItem()[0]) == L'\0')
cout << "works as expected";

return 0;
}

<<snap



If I compile it using g++ 4.1.2, I get:

test.cpp: In function 'int main()':
test.cpp:53: error: ISO C++ says that these are ambiguous, even though
the worst conversion for the first is better than the worst conversion
for the second:
test.cpp:24: note: candidate 1: wchar_t ConfigItem:perator[](int)
const
test.cpp:32: note: candidate 2: ConfigItem& ConfigItem:perator[]
(const std::wstring&)
test.cpp:53: error: ISO C++ says that these are ambiguous, even though
the worst conversion for the first is better than the worst conversion
for the second:
test.cpp:24: note: candidate 1: wchar_t ConfigItem:perator[](int)
const
test.cpp:29: note: candidate 2: ConfigItem& ConfigItem:perator[]
(const wchar_t*)



On which path does ISO C++/the compiler deduct the second
candidates?? Now for the really (to me) weird part:

If I remove the function const from wchar_t operator[](int pos) const
so it reads

wchar_t operator[](int pos) {

above code works as expected and no ambiguity error is shown, the
following does also work

const wchar_t operator[](const int pos) {


It is just the function const that provokes the ambiguity - why?



Many thx for an insightful reply, I spent hours on this and don't
really have a clue, why making an overloaded operator function-const
opens paths to the ambiguity shown.

Btw, this is not the full code, just the minimal part to make the
mistake happen



Thanks much,
Christian Müller
 
Reply With Quote
 
 
 
 
Eric Pruneau
Guest
Posts: n/a
 
      06-02-2008

<(E-Mail Removed)> a écrit dans le message de news:
ef9c8d94-fe62-4991-a0b7-ac7a62d51cc1...oglegroups.com...
Hi,


I have the following code and trouble with ambiguity due to operator
overloading..

The code is also at http://paste.nn-d.de/441

snip>>

#include <iostream>
#include <string>
#include <map>

using namespace std;


class ConfigItem;
typedef map<wstring, ConfigItem> ConfigMap;

class ConfigItem {

public:
ConfigItem() { type=NONE; s[0]=0; }


ConfigItem(const wchar_t *str) {
type=STRING;
wcscpy(s, str);
}
operator const wchar_t*() const {
return s;
}
wchar_t operator[](int pos) const {
return (operator const wchar_t*())[pos];
}


ConfigItem& operator[](const wchar_t *option) {
return operator[](wstring(option));
}
ConfigItem& operator[](const wstring &option) {
switch (type) {
case MAP: return (*cm)[option];
default: return *this;
}
}

private:
enum {
NONE,
INT,
STRING,
MAP,
} type;

wchar_t s[512];
ConfigMap *cm;
};


int main() {
if (wchar_t(ConfigItem()[0]) == L'\0')
cout << "works as expected";

return 0;
}

<<snap



If I compile it using g++ 4.1.2, I get:

test.cpp: In function 'int main()':
test.cpp:53: error: ISO C++ says that these are ambiguous, even though
the worst conversion for the first is better than the worst conversion
for the second:
test.cpp:24: note: candidate 1: wchar_t ConfigItem:perator[](int)
const
test.cpp:32: note: candidate 2: ConfigItem& ConfigItem:perator[]
(const std::wstring&)
test.cpp:53: error: ISO C++ says that these are ambiguous, even though
the worst conversion for the first is better than the worst conversion
for the second:
test.cpp:24: note: candidate 1: wchar_t ConfigItem:perator[](int)
const
test.cpp:29: note: candidate 2: ConfigItem& ConfigItem:perator[]
(const wchar_t*)



On which path does ISO C++/the compiler deduct the second
candidates?? Now for the really (to me) weird part:

If I remove the function const from wchar_t operator[](int pos) const
so it reads

wchar_t operator[](int pos) {

above code works as expected and no ambiguity error is shown, the
following does also work

const wchar_t operator[](const int pos) {


It is just the function const that provokes the ambiguity - why?



Many thx for an insightful reply, I spent hours on this and don't
really have a clue, why making an overloaded operator function-const
opens paths to the ambiguity shown.

Btw, this is not the full code, just the minimal part to make the
mistake happen



Thanks much,
Christian Müller



ok first try this :

if (wchar_t(ConfigItem()[1]) == L'\0') // 0 changed by 1
cout << "works as expected";

you gonna see that it work. That is because using 0 could be interpreted as
a NULL pointer and that is the reason the compiler is considering the
function with a wchar_t *.

you can do :

int zero = 0;
if (wchar_t(ConfigItem()[zero]) == L'\0') // 0 changed by 1
cout << "works as expected";

than it will work.

Now for the const thing.
First, the return value is never used in argument deduction, so adding a
const to the return value does not change anything, but it does if you make
the function const.


This should work fine

const ConfigItem c;
if (wchar_t(c[0]) == L'\0')
cout << "works as expected";

cause c is const and therefore no ambiguity here since the function is
const.




 
Reply With Quote
 
 
 
 
abendstund@gmail.com
Guest
Posts: n/a
 
      06-02-2008
> ok first try this :
>
> if (wchar_t(ConfigItem()[1]) == L'\0') // 0 changed by 1
> cout << "works as expected";
>
> you gonna see that it work. That is because using 0 could be interpreted as
> a NULL pointer and that is the reason the compiler is considering the
> function with a wchar_t *.
>
> you can do :
>
> int zero = 0;
> if (wchar_t(ConfigItem()[zero]) == L'\0') // 0 changed by 1
> cout << "works as expected";
>
> than it will work.
>


Thanks for your answer, but at least the first part is bogus, first
statement is not true - it does not matter if i use 0 or 1, the
ambiguity is even present if I do:

if (wchar_t(ConfigItem()[int(0)]) == L'\0') // explicit cast to int
cout << "works as expected";


> Now for the const thing.
> First, the return value is never used in argument deduction, so adding a
> const to the return value does not change anything, but it does if you make
> the function const.
>
> This should work fine
>
> const ConfigItem c;
> if (wchar_t(c[0]) == L'\0')
> cout << "works as expected";
>
> cause c is const and therefore no ambiguity here since the function is
> const.


Ok this works, but I was under the impression that making a function
const just makes the compiler enforce my intention to not change any
values outside of the scope of this function.. you say it does make a
difference, but in what way. After all, I am allowed to call the
const function from a non-const ConfigItem. So why is it ambiguous?

Regards and thx,
Christian
 
Reply With Quote
 
James Kanze
Guest
Posts: n/a
 
      06-02-2008
On Jun 1, 10:38 pm, (E-Mail Removed) wrote:
> I have the following code and trouble with ambiguity due to
> operator overloading..


> The code is also athttp://paste.nn-d.de/441


> snip>>


> #include <iostream>
> #include <string>
> #include <map>


> using namespace std;


> class ConfigItem;
> typedef map<wstring, ConfigItem> ConfigMap;


> class ConfigItem {


> public:
> ConfigItem() { type=NONE; s[0]=0; }


> ConfigItem(const wchar_t *str) {
> type=STRING;
> wcscpy(s, str);
> }
> operator const wchar_t*() const {
> return s;
> }
> wchar_t operator[](int pos) const {
> return (operator const wchar_t*())[pos];
> }
>
> ConfigItem& operator[](const wchar_t *option) {
> return operator[](wstring(option));
> }
> ConfigItem& operator[](const wstring &option) {
> switch (type) {
> case MAP: return (*cm)[option];
> default: return *this;
> }
> }


> private:
> enum {
> NONE,
> INT,
> STRING,
> MAP,
> } type;


> wchar_t s[512];
> ConfigMap *cm;
> };


> int main() {
> if (wchar_t(ConfigItem()[0]) == L'\0')
> cout << "works as expected";
> return 0;


> }


> <<snap


> If I compile it using g++ 4.1.2, I get:
>
> test.cpp: In function 'int main()':
> test.cpp:53: error: ISO C++ says that these are ambiguous, even though
> the worst conversion for the first is better than the worst conversion
> for the second:
> test.cpp:24: note: candidate 1: wchar_t ConfigItem:perator[](int)
> const
> test.cpp:32: note: candidate 2: ConfigItem& ConfigItem:perator[]
> (const std::wstring&)
> test.cpp:53: error: ISO C++ says that these are ambiguous, even though
> the worst conversion for the first is better than the worst conversion
> for the second:
> test.cpp:24: note: candidate 1: wchar_t ConfigItem:perator[](int)
> const
> test.cpp:29: note: candidate 2: ConfigItem& ConfigItem:perator[]
> (const wchar_t*)


> On which path does ISO C++/the compiler deduct the second
> candidates??


For the operator[], the compiler considers two arguments, the
object on which it is going to be called (the argument which
becomes the this pointer), and the index argument. In your
expression, ConfigItem()[0], you have a (non-const) ConfigItem,
and a constant integral expression evaluating to 0. Both
operator[]( int ) const and operator[]( wchar_t* ) can be
called. For the first argument, the second is the better match,
because the first requires a qualifier conversion. For the
second argument, the first is a better match, because it is an
exact match. The result is that the call is ambiguous.

> Now for the really (to me) weird part:


> If I remove the function const from wchar_t operator[](int
> pos) const so it reads


> wchar_t operator[](int pos) {


> above code works as expected and no ambiguity error is shown,


Yes. Because now, you have a better match for the second
argument, and the first two are equal (both exact matches).

> the following does also work


> const wchar_t operator[](const int pos) {


This is the same as the above.

> It is just the function const that provokes the ambiguity - why?


Because it means that calling the function on a non-const object
requires a qualifier conversion.

> Many thx for an insightful reply, I spent hours on this and
> don't really have a clue, why making an overloaded operator
> function-const opens paths to the ambiguity shown.


It *is* sometimes surprising. But frankly, I'd wonder about so
many overloads. What does [] mean on an object of your class?
Off hand, I'd say that if you have [] whose return type differs
in more than just const, then you have operator overload abuse:
if there's a natural meaning for [], then that will exclusively
determine the return type, and if there's not, then you
shouldn't use [].

--
James Kanze (GABI Software) email:(E-Mail Removed)
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
 
Reply With Quote
 
James Kanze
Guest
Posts: n/a
 
      06-02-2008
On Jun 2, 4:44 am, (E-Mail Removed) wrote:
> > ok first try this :


> > if (wchar_t(ConfigItem()[1]) == L'\0') // 0 changed by 1
> > cout << "works as expected";


> > you gonna see that it work. That is because using 0 could be interpreted as
> > a NULL pointer and that is the reason the compiler is considering the
> > function with a wchar_t *.


> > you can do :


> > int zero = 0;
> > if (wchar_t(ConfigItem()[zero]) == L'\0') // 0 changed by 1
> > cout << "works as expected";


> > than it will work.


> Thanks for your answer, but at least the first part is bogus, first
> statement is not true - it does not matter if i use 0 or 1, the
> ambiguity is even present if I do:


> if (wchar_t(ConfigItem()[int(0)]) == L'\0') // explicit cast to int
> cout << "works as expected";


Which still has the 0. Use 1 instead of 0, and the ambiguity
disappears. Use anything but a constant integral expression,
and the ambiguity disappears.

--
James Kanze (GABI Software) email:(E-Mail Removed)
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
 
Reply With Quote
 
abendstund@gmail.com
Guest
Posts: n/a
 
      06-02-2008
> For the operator[], the compiler considers two arguments, the
> object on which it is going to be called (the argument which
> becomes the this pointer), and the index argument. In your
> expression, ConfigItem()[0], you have a (non-const) ConfigItem,
> and a constant integral expression evaluating to 0. Both
> operator[]( int ) const and operator[]( wchar_t* ) can be
> called. For the first argument, the second is the better match,
> because the first requires a qualifier conversion. For the
> second argument, the first is a better match, because it is an
> exact match. The result is that the call is ambiguous.


Ok I get it, it's the need for a qualifier conversion of the first
argument (the ConfigItem obj) if I call a const function on a non-
const obj that I missed out on. Thx! This implies that if I can
make all the op[] functions const the ambiguity is gone as well.

Now the only thing still shaky in my understanding is the 0/1 part - I
am with you to the point that constant integral types can be
implicitly converted to pointer types by the compiler in case they are
zero. The difference in the following two is left to be understood:

WORKS:
ConfigItem c(L"\0");
int zero = 0;
if (wchar_t(c[zero]) == L'\0')
cout << "works as expected";

COMPILE-TIME-ERROR:
ConfigItem c(L"\0");
if (wchar_t(c[int(0)]) == L'\0')
cout << "works as expected";

If I tell the compiler to explicitely use the constant integral 0 as
an integer, why is it still considering conversion to a pointer type??


Thx for all your help,
it's appreciated,
regards,

Christian


ps: The overloads are mostly for convenience - the ConfigItem can
either hold a string, an int or a map(string, ConfigItem) - in case it
holds a map I want ConfigItem's op[] to return map's op[] (which
returns a ConfigItem again) - in case it holds a string I want op[int]
to just be a shortcut to saying ((const wchar_t*)items[L"optname"])
[int] -- items[L"optname"][int] looks much nicer, and it's still
intuitive to me

op[int] on a map does not make sense in my case as the key for the map
is of type string (in it's wc version wstring)
 
Reply With Quote
 
James Kanze
Guest
Posts: n/a
 
      06-03-2008
On Jun 2, 6:34 pm, (E-Mail Removed) wrote:
> > For the operator[], the compiler considers two arguments, the
> > object on which it is going to be called (the argument which
> > becomes the this pointer), and the index argument. In your
> > expression, ConfigItem()[0], you have a (non-const) ConfigItem,
> > and a constant integral expression evaluating to 0. Both
> > operator[]( int ) const and operator[]( wchar_t* ) can be
> > called. For the first argument, the second is the better match,
> > because the first requires a qualifier conversion. For the
> > second argument, the first is a better match, because it is an
> > exact match. The result is that the call is ambiguous.


> Ok I get it, it's the need for a qualifier conversion of the
> first argument (the ConfigItem obj) if I call a const function
> on a non- const obj that I missed out on. Thx! This implies
> that if I can make all the op[] functions const the ambiguity
> is gone as well.


It should be.

> Now the only thing still shaky in my understanding is the 0/1
> part - I am with you to the point that constant integral types
> can be implicitly converted to pointer types by the compiler
> in case they are zero. The difference in the following two is
> left to be understood:


> WORKS:
> ConfigItem c(L"\0");
> int zero = 0;
> if (wchar_t(c[zero]) == L'\0')
> cout << "works as expected";


> COMPILE-TIME-ERROR:
> ConfigItem c(L"\0");
> if (wchar_t(c[int(0)]) == L'\0')
> cout << "works as expected";


> If I tell the compiler to explicitely use the constant
> integral 0 as an integer, why is it still considering
> conversion to a pointer type??


Because "int(0)" is a "constant integral expression with the
value of 0". In fact, there is absolutely no different from the
compiler's point of view between "0" and "int(0)". Both are
expressions. Both have type int. And both have the constant
value 0.

Note that if in the example above, you change the declaration of
the variable to:
int const zero = 0 ;
it will stop working as well. Because a const variable of
integral type, initialized with a constant integral expression,
is also a constant integral expression.

It's a serious wart in the language, but I think we have to live
with it. Anything that would fix it would break so much code as
to not be possible.

[...]
> ps: The overloads are mostly for convenience - the ConfigItem can
> either hold a string, an int or a map(string, ConfigItem) - in case it
> holds a map I want ConfigItem's op[] to return map's op[] (which
> returns a ConfigItem again) - in case it holds a string I want op[int]
> to just be a shortcut to saying ((const wchar_t*)items[L"optname"])
> [int] -- items[L"optname"][int] looks much nicer, and it's still
> intuitive to me


It sounds like a potential source of errors to me. I'd have
distinctly named functions for accessing each type, so that the
client code must visibly express the type it thinks is contained
in the ConfigItem.

--
James Kanze (GABI Software) email:(E-Mail Removed)
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
 
Reply With Quote
 
abendstund@gmail.com
Guest
Posts: n/a
 
      06-06-2008
Thanks much for all your insight..

> It sounds like a potential source of errors to me. I'd have
> distinctly named functions for accessing each type, so that the
> client code must visibly express the type it thinks is contained
> in the ConfigItem.


You're right, this is a potential source for errors, but a nice
academic playground to learn the language - it's not really a well
thought design (yet)..


Best regards,
Christian

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Problem in calling an overloaded operator= from inside anotheroverloaded operator= Afshin C++ 7 08-16-2011 12:46 PM
Problem in calling an overloaded operator= from inside anotheroverloaded operator= in C++ Afshin C++ 0 08-12-2011 12:19 PM
Problem in calling an overloaded operator= from inside anotheroverloaded operator Afshin C++ 0 08-12-2011 12:01 PM
ambiguity related to overloaded functions subramanian100in@yahoo.com, India C++ 2 06-25-2007 07:49 AM
"overloaded cast operator" and "operator const" John Goche C++ 2 09-04-2006 02:48 PM



Advertisments