Ambiguity by making overloaded operator function-const - why?

Discussion in 'C++' started by abendstund@gmail.com, Jun 1, 2008.

  1. Guest

    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
    , Jun 1, 2008
    #1
    1. Advertising

  2. Eric Pruneau Guest

    <> a écrit dans le message de news:
    ...
    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.
    Eric Pruneau, Jun 2, 2008
    #2
    1. Advertising

  3. Guest

    Re: Ambiguity by making overloaded operator function-const - why?

    > 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
    , Jun 2, 2008
    #3
  4. James Kanze Guest

    Re: Ambiguity by making overloaded operator function-const - why?

    On Jun 1, 10:38 pm, 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::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 [].

    --
    James Kanze (GABI Software) email:
    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
    James Kanze, Jun 2, 2008
    #4
  5. James Kanze Guest

    Re: Ambiguity by making overloaded operator function-const - why?

    On Jun 2, 4:44 am, 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:
    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
    James Kanze, Jun 2, 2008
    #5
  6. Guest

    Re: Ambiguity by making overloaded operator function-const - why?

    > 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)
    , Jun 2, 2008
    #6
  7. James Kanze Guest

    Re: Ambiguity by making overloaded operator function-const - why?

    On Jun 2, 6:34 pm, 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:
    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
    James Kanze, Jun 3, 2008
    #7
  8. Guest

    Re: Ambiguity by making overloaded operator function-const - why?

    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
    , Jun 6, 2008
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Mr. SweatyFinger
    Replies:
    2
    Views:
    1,839
    Smokey Grindel
    Dec 2, 2006
  2. John Goche
    Replies:
    2
    Views:
    343
    Frederick Gotham
    Sep 4, 2006
  3. , India
    Replies:
    2
    Views:
    257
    Alan Johnson
    Jun 25, 2007
  4. Javier
    Replies:
    2
    Views:
    559
    James Kanze
    Sep 4, 2007
  5. Vladimir Menshakov
    Replies:
    1
    Views:
    359
Loading...

Share This Page