Ambiguity by making overloaded operator function-const - why?

A

abendstund

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::eek:perator[](int)
const
test.cpp:32: note: candidate 2: ConfigItem& ConfigItem::eek: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::eek:perator[](int)
const
test.cpp:29: note: candidate 2: ConfigItem& ConfigItem::eek: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
 
E

Eric Pruneau

<[email protected]> a écrit dans le message de
(e-mail address removed)...
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::eek:perator[](int)
const
test.cpp:32: note: candidate 2: ConfigItem& ConfigItem::eek: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::eek:perator[](int)
const
test.cpp:29: note: candidate 2: ConfigItem& ConfigItem::eek: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.
 
A

abendstund

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
 
J

James Kanze

I have the following code and trouble with ambiguity due to
operator overloading..
The code is also athttp://paste.nn-d.de/441

#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;

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::eek:perator[](int)
const
test.cpp:32: note: candidate 2: ConfigItem& ConfigItem::eek: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::eek:perator[](int)
const
test.cpp:29: note: candidate 2: ConfigItem& ConfigItem::eek: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 [].
 
J

James Kanze

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.
 
A

abendstund

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)
 
J

James Kanze

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.
 
A

abendstund

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
 

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,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top