views:

565

answers:

7
template <typename T>
class v3 {
private:
    T _a[3];

public:
    T & operator [] (unsigned int i) { return _a[i]; }
    const T & operator [] (unsigned int i) const { return _a[i]; }

    operator T * () { return _a; }
    operator const T * () const { return _a; }

    v3() {
     _a[0] = 0; // works
     _a[1] = 0;
     _a[2] = 0;
    }

    v3(const v3<T> & v) {
     _a[0] = v[0]; // Error 1 error C2666: 'v3<T>::operator []' : 2 overloads have similar conversions
     _a[1] = v[1]; // Error 2 error C2666: 'v3<T>::operator []' : 2 overloads have similar conversions
     _a[2] = v[2]; // Error 3 error C2666: 'v3<T>::operator []' : 2 overloads have similar conversions
    }
};

int main(int argc, char ** argv)
{
    v3<float> v1;
    v3<float> v2(v1);

    return 0;
}
A: 

The const version doesn't modify anything. The non-const version allows you to assign things using array notation (v[3] = 0.5;).

Stephen Newell
+4  A: 

If you read the rest of the error message (in the output window), it becomes a bit clearer:

1>        could be 'const float &v3<T>::operator [](unsigned int) const'
1>        with
1>        [
1>            T=float
1>        ]
1>        or       'built-in C++ operator[(const float *, int)'
1>        while trying to match the argument list '(const v3<T>, int)'
1>        with
1>        [
1>            T=float
1>        ]

The compiler can't decide whether to use your overloaded operator[] or the built-in operator[] on the const T* that it can obtain by the following conversion function:

operator const T * () const { return _a; }

Both of the following are potentially valid interpretations of the offending lines:

v.operator float*()[0]
v.operator[](0)

You can remove the ambiguity by explicitly casting the integer indices to be unsigned so that no conversion is needed:

_a[0] = v[static_cast<unsigned int>(0)];

or by changing your overloaded operator[]s to take an int instead of an unsigned int, or by removing the operator T*() const (and probably the non-const version too, for completeness).

James McNellis
How do you read that declaration? "T is a constant pointer to a function that takes no arguments and .... "
Shakedown
To be more precise, the reason why there is an ambiguity is because e.g. literal `1` (used as an index) is of type `int`, not `unsigned int`, so it requires a conversion to call the overloader `operator[](unsigned int)`. In other words, two alternatives are `v.operator float*()[0]` and `v.operator[]((unsigned int)0)`.
Pavel Minaev
@Shakedown: which declaration?
Pavel Minaev
It's a conversion operator that converts a v3<T> to a pointer to a const T. An operator() would have declaration `const T* operator()();`
outis
Shakedown: Do you mean `operator T*() const`? If so you interpret that as `a const conversion operator returning 'pointer to T'`.
jmucchiello
jmucchiello's got it; I left out the fact that the method is const.
outis
@Pavel Minaev: Thank you; when I pasted the full error, I overlooked that his `operator[]` was taking an `unsigned int`, not an `int`.
James McNellis
It may also be useful to explain the role of `ptrdiff_t`: The error message only shows half of the truth by displaying a naked `int` - but it's `ptrdiff_t`, which happens to be typedef'ed to `int` on that compiler. On another system, `ptrdiff_t` could be `long`, at which point you would face a conversion for both the builtin `op[]` and your own one. In that case, your own one would be taken, because it matches better for the first argument (the object on which the index operation is done - `*this`) and equal for the second argument.
Johannes Schaub - litb
+2  A: 

In simple terms: the compiler doesn't know whether to convert v to const float* and then use the operator[] for a pointer, or to convert 0 to unsigned int and then use the operator[] for const v3.

Fix is probably to remove the operator[]. I can't think of anything it gives you that the conversion operator to T* doesn't already. If you were planning to put some bounds-checking in operator[], then I'd say replace the conversion operators with getPointer functions (since in general you don't want to implicitly convert a safe thing to an unsafe thing), or do what std::vector does, which is that users get a pointer with &v[0].

Another change which lets it compile, is to change operator[] to take an int parameter instead of unsigned int. Then in your code, the compiler unambiguously chooses the interpretation with no conversion. According to my compiler, there is still no ambiguity even when using an unsigned index. Which is nice.

Steve Jessop
+1  A: 

It is your type conversion operator that is the culprit. v transformed to a float pointer. Now there are two operator []s possible, one is the built in subscript operator for float and the other being the one you defined on v, which one should the language pick, so it is an ambiguity according to ISO.

Murali VP
+1  A: 

Remember a class is a friend of itself:

v3(const v3<T> & v)
{
     _a[0] = v._a[0]; 
     _a[1] = v._a[1]; 
     _a[2] = v._a[2];
}

When copy something of the same type you are already exposed to the implementation details. Thus it is not a problem to access the implementation directly if that is appropriate. So from the constructor you can access the object you are copying directly and see its member '_a'.

If you want to know the original problem:

The literal '1' in the context 'v[1]' is an integer (this is a synonym of signed integer). Thus to use the operator[] the compiler technically is required to insert a conversion from int to unisgned. The other alternative is to use the operator*() to get a pointer to the internal object and then use the [] operator on the pointer. Compiler is not allowed to make this choice and error out:

Compiler options:

 _a[1] = v[1];
 // Options 1:
 _a[1] = v.operator[]((unsigned int)1);
 // Options 2:
 _a[1] = v.operator*()[1];

To make it unabigious you can use an unsigned literal;

 _a[1] = v[1u];

In the long run it may be worth making this easier for the user.
Convert the operator[] to use int rather than unsigned int then you will get exact matches when integer literals (or you can have two sets of operator[]. One that uses int and one that uses unsigned int).

Martin York
+1  A: 

When you the compiler compiles the following

v[0]

it has to consider two possible interpretations

v.operator T*()[0] // built-in []
v.operator[](0)    // overloaded []

Neither candidate is better than the other because each one requires a conversion. The first variant requires a user-defined conversion from v3<T> to T*. The second variant requires a standard conversion from int (0 is int) to unsigned int, since your overloaded [] requires an unsigned int argument. This makes these candidates uncomparable (neither is clearly better by C++ rules) and thus makes the call ambuguous.

If you invoke the operator as

v[0U]

the ambiguity will disappear (since 0U is already an unsigned int) and your overloaded [] will be selected. Alternatively, you can declare your overloaded [] with int argument. Or you can remove the conversion operator entirely. Or do something else to remove the ambiguity - you decide.

AndreyT
@litb: That's what I mant by the word *uncomparable*, which is, if I remember correctly, the formal term that describes the relationship between such elemens in partial orderings in mathematics.
AndreyT
@litb: P.S. I should have used *incomparable*, as I see now... :)))
AndreyT
@AndreyT, well i read "each one requires a conversion ... this makes these candidates uncomparable" as saying both conversions are equally bad. So i thought i point out that it is only ambiguous because of the different positions: Different positions cannot be compared.
Johannes Schaub - litb
A: 

I didn't see it untill James McNellis posted the full error message, but the ambiguity is not between the two v3::operator[]() functions as it appears to be.

Instead, since there is no exact match between argument types, the compiler can't decide whether to:

a) Use v3::operator[](unsigned int) const, thereby converting the int argument to unsigned, or

b) Use the v3::operator const T*() const conversion followed by the built-in array indexing operator.

You can avoid this by making the operator[] arguments int's rather than unsigned ints. But a better solution would be to avoid an implicit conversion to T* and instead provide a Data() function that did that explicitly.

Drew Hall