Skip to main content
gamedev.net
🔒 Locked

MSVC operator problem

Started by rozz666 19 replies 2.8k views
Original Post
rozz666
rozz666
I tried using MS VC++ 2005 express, but resigned due to this error:

class X {
private:

    int xxx;

public:

    operator int *() { return &xxx }
    int operator[](int i) { return xxx; }

    void test()
    {
        X x;
        unsigned idx = 5;

        x[idx];  // C2666
    }
};
error C2666: 'X::operator []' : 2 overloads have similar conversions d:\documents and settings\rp\moje dokumenty\visual studio 2005\projects\con_test\con_test\con_test.cpp(15): could be 'int X::operator [](int)' or 'built-in C++ operator[(int *, unsigned int)' while trying to match the argument list '(X, unsigned int)' It compiles under Borland Turbo C++, gcc, DevC++. Does anyone know if it's correct by means of ANSI C++?
joanusdmentia
joanusdmentia
VS2005 is correct, and this is why providing both operator[] and a pointer cast operator is a bad idea (typical example being a string class). What's happening is your operator[] is expecting a signed int whereas you're passing in an unsigned. Normally this would be fine, the unsigned is cast to a signed (with a warning on higher warning levels) and operator[] is called. When you add the pointer cast operator though you now have 2 possible matches that both require only 1 cast, thus the error about ambiguity.

What version of GCC are you using? If it's recent I'm surprised it's letting that through.
"Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The nly verdict is vengeance; a vendett o
ToohrVyk
ToohrVyk
The VC++ ambiguity error seems to me to be the sane behaviour, though I don't know what the standard behaviour is. I'd say it depends on whether the standard considers a to be a binary expression operator (like +) or a member operator (like =).

As for the actual code, either the operator[] is redundant (it could be removed) or it has a different behaviour from casting to int* and subscripting into that buffer, at which point pain and misery due to implicit conversions are pretty much guaranteed in the near future.
rozz666
rozz666
Maybe someone has a Comeau compiler to check it? :-)
I'd don't remember which version of gcc it is, but it came with openSUSE 10.2, so it's probably one of the newest.
As for the ambiguity, I understand why VC doesn't like it. The problem is, is it the way it should work. Theoretically, I could make:

class X {public:    int operator[](int idx} { // ... }    int& operator*() { // ... }    X operator+(int i) { // ... }};


and have ambiguity here too, since x[n] == *(x + n).
But that's not the point. If I have operator[] defined VC shouldn't use cast operator.
rozz666
rozz666
Quote:
Original post by joanusdmentia
VS2005 is correct, and this is why providing both operator[] and a pointer cast operator is a bad idea (typical example being a string class).


It's indeed for a string class. However it's not redundant, since I perform range checking in operator[] so I need separate operator const char * and operator[].
ToohrVyk
ToohrVyk
Quote:
Original post by rozz666
If I have operator[] defined VC shouldn't use cast operator.


Exactly. But since you don't have operator[] defined, VC starts trying to make casts until it finds a matching definition. Since it finds two, it complains.

If you had the correct int * operator[](unsigned) defined, it would have been used. Expecting operator[](int) to be used in place of operator[](unsigned) is, to the compiler, the same as expecting operator[](int) to be used in place of operator[](const std::vector<enum {TRUE, FALSE, FILE_NOT_FOUND}> &): it will work at the cost of a type cast, whenever that cast is possible.
rozz666
rozz666
Quote:
Original post by ToohrVyk
Quote:
Original post by rozz666
If I have operator[] defined VC shouldn't use cast operator.


Exactly. But since you don't have operator[] defined, VC starts trying to make casts until it finds a matching definition. Since it finds two, it complains.

If you had the correct int * operator[](unsigned) defined, it would have been used.


Having operator[](unsigned) defined is also ambiguous, isn't it?
There are still 2 possibilities.
ToohrVyk
ToohrVyk
Quote:
Original post by rozz666
Having operator[](unsigned) defined is also ambiguous, isn't it?
There are still 2 possibilities.


No. There is only one single possibility which may be used with exactly zero type casts, and that would be int X::operator[](unsigned). Any other possibilities require one or more type casts, and are thus excluded from the search. VC++ is perfectly happy with that, too.

The real question is whether type-casts should be attempted on the left-hand side of operator[] or not. Both Visual C++ and g++ agree on that point (because, if you remove the operator[] version, both will use the cast). Therefore, g++ has no excuse for not complaining (or barely mentioning) the ambiguity—the only two sane behaviours here are preventing type-casts to the left of operator[], or detecting ambiguity.

dmatter
dmatter
Take a look at the STL's std::string,

1) Use it

2) Instead of overloading the pointer operator it has a function c_str(), not only does this make it more explicit what's happening, which I feel in this case is a good-thing, but it avoids the ambiguity.
rozz666
rozz666
Quote:
Original post by ToohrVyk
Quote:
Original post by rozz666
Having operator[](unsigned) defined is also ambiguous, isn't it?
There are still 2 possibilities.


No. There is only one single possibility which may be used with exactly zero type casts, and that would be int X::operator[](unsigned). Any other possibilities require one or more type casts, and are thus excluded from the search. VC++ is perfectly happy with that, too.

The real question is whether type-casts should be attempted on the left-hand side of operator[] or not. Both Visual C++ and g++ agree on that point (because, if you remove the operator[] version, both will use the cast). Therefore, g++ has no excuse for not complaining (or barely mentioning) the ambiguity—the only two sane behaviours here are preventing type-casts to the left of operator[], or detecting ambiguity.


Then I presume pointers have operator[] defined for all signed and unsigned integer types or at least int and unsigned?
But there's one thing I don't understand.
Suppose I have:

class X {public:    void f(unsigned);};class Y {public:    void f(int);    operator X();};void test(){    Y y;    y.f((unsigned) 5); // Y::f or X::f ?}


Do you see my point?
It's obvious that's Y::f. Why operator[] isn't working the same way?
rozz666
rozz666
Quote:
Original post by dmatter
Take a look at the STL's std::string,

1) Use it

2) Instead of overloading the pointer operator it has a function c_str(), not only does this make it more explicit what's happening, which I feel in this case is a good-thing, but it avoids the ambiguity.


I can't use std::string, because I need some more functionality than it has. I know I could inherit from std::string, that wouldnt spare my much time implementing my string class. Moreover, I use expression templates for operator+, so I really have no benefit from using std::string.
ToohrVyk
ToohrVyk
Quote:
Original post by rozz666
Do you see my point?
It's obvious that's Y::f. Why operator[] isn't working the same way?


I agree that it doesn't make sense—to me, <edit>implicit</edit> casting on the left-hand side of operator[] should be forbidden, as with all other binary operators which accept lvalues as their left members. Neither Visual C++ nor g++ manage to get this right, though—g++ is worse than VC in this regard, because it's not consistent: it acts as if casting was forbidden when it would create an ambiguity, and acts as if it was allowed otherwise.
rozz666
rozz666
Quote:
Original post by ToohrVyk
Quote:
Original post by rozz666
Do you see my point?
It's obvious that's Y::f. Why operator[] isn't working the same way?


I agree that it doesn't make sense—to me, <edit>implicit</edit> casting on the left-hand side of operator[] should be forbidden, as with all other binary operators which accept lvalues as their left members. Neither Visual C++ nor g++ manage to get this right, though—g++ is worse than VC in this regard, because it's not consistent: it acts as if casting was forbidden when it would create an ambiguity, and acts as if it was allowed otherwise.


So now, that we agree that it doesn't make sense, we need to find out what's the standard says about it. It means buying ANSI sheet or Comeau compiler, so I hope someone in this forum hopefully has one of these?
ToohrVyk
ToohrVyk
Quote:
Original post by rozz666
So now, that we agree that it doesn't make sense, we need to find out what's the standard says about it. It means buying ANSI sheet or Comeau compiler, so I hope someone in this forum hopefully has one of these?


There's also the standard draft, which is freely available online.

In particular:

Quote:
13.5.5 §1
operator[] shall be a non-static member function with exactly one
parameter. It implements the subscripting syntax
postfix-expression [ expression ]

Thus, a subscripting expression x[y] is interpreted as x.operator[](y)
for a class object x of type T if T::operator[](T1) exists and if the
operator is selected as the best match function by the overload reso-
lution mechanism (_over.match.best_).


And:

Quote:
13.3.1.2
§4 For the built-in assignment operators, conversions of the left operand
are restricted as follows:

--no temporaries are introduced to hold the left operand, and

--no user-defined conversions are applied to the left operand to
achieve a type match with the left-most parameter of a built-in can-
didate.

§5 For all other operators, no such restrictions apply.


Hm. So, for every possible conversion, the compiler determines if an user-defined or built-in operator exists, and chooses the least ambiguous one, and the = operator is restricted.

I have to leave now, so I'll let the rest of the reading to you. [smile]
rozz666
rozz666
Quote:
Original post by ToohrVyk
Quote:
Original post by rozz666
So now, that we agree that it doesn't make sense, we need to find out what's the standard says about it. It means buying ANSI sheet or Comeau compiler, so I hope someone in this forum hopefully has one of these?


There's also the standard draft, which is freely available online.

In particular:

Quote:
13.5.5 §1
operator[] shall be a non-static member function with exactly one
parameter. It implements the subscripting syntax
postfix-expression [ expression ]

Thus, a subscripting expression x[y] is interpreted as x.operator[](y)
for a class object x of type T if T::operator[](T1) exists and if the
operator is selected as the best match function by the overload reso-
lution mechanism (_over.match.best_).


And:

Quote:
13.3.1.2
§4 For the built-in assignment operators, conversions of the left operand
are restricted as follows:

--no temporaries are introduced to hold the left operand, and

--no user-defined conversions are applied to the left operand to
achieve a type match with the left-most parameter of a built-in can-
didate.

§5 For all other operators, no such restrictions apply.


Hm. So, for every possible conversion, the compiler determines if an user-defined or built-in operator exists, and chooses the least ambiguous one, and the = operator is restricted.

I have to leave now, so I'll let the rest of the reading to you. [smile]



Thanks, I'll check it out.
ToohrVyk
ToohrVyk
g++ is right!

Quote:
13.3.3.2
§2 When comparing the basic forms of implicit conversion sequences (as
defined in _over.best.ics_)

--a standard conversion sequence (_over.ics.scs_) is a better conver-
sion sequence than a user-defined conversion sequence or an ellipsis
conversion sequence, and

--a user-defined conversion sequence (_over.ics.user_) is a better
conversion sequence than an ellipsis conversion sequence
(_over.ics.ellipsis_).


So, the X → int* conversion is strictly worse than unsigned → int, and there should be no ambiguity.

In the end, judging from our human difficulty to resolve the ambiguity, I'd say it's pretty dangerous to leave it this way.
Spoonbender
Spoonbender
Quote:
Original post by rozz666
I can't use std::string, because I need some more functionality than it has.

Like what?
Zahlman
Zahlman
Quote:
Original post by rozz666
I can't use std::string, because I need some more functionality than it has.


What kind of functionality?

Quote:
I know I could inherit from std::string, that wouldnt spare my much time implementing my string class.


Have you tried? You might be surprised. (Also consider composition instead of inheritance.) If you have to get at a lower level (e.g. because of the next part...), at least consider if std::vector is useful for storage.

Quote:
Moreover, I use expression templates for operator+, so I really have no benefit from using std::string.


To avoid multiple reallocation? Oh, that sounds like a pretty major project. x.x
dmatter
dmatter
Quote:
Original post by rozz666
I can't use std::string, because I need some more functionality than it has. I know I could inherit from std::string, that wouldnt spare my much time implementing my string class. Moreover, I use expression templates for operator+, so I really have no benefit from using std::string.

If you need to extend a standard container then aggregate it or write some custom algorithms.
You can let std::string handle 90% of the string management for you. If you think about it with std::string all the memory handling and most of the funtionality is already there, you can use that and just build from there.

Example aggregation:
class MyString{public:    // Extra functionality.protected:    std::string _string;};

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.