Sign in to follow this  
rozz666

MSVC operator problem

Recommended Posts

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++?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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[b] 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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[].

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 Xint* conversion is strictly worse than unsignedint, 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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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;
};

Share this post


Link to post
Share on other sites
I use my own array class with reference counting for memory management. I don't realy need std::string. It's all written anyway. I added conversions to all numeric types operator+ and += for them. operator+ is written using expression templates to avoid multiple allocations.

It's a research project. I tried compiling it under VC++ because I heard that it has better optimizations than Borland. But anyways, the target OS is QNX so now I just got one more reason not to use VC++ :-) Despite being slower, I think Borland has also been doingthings according to standards unlike MS.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this