Ошибка C ++: оператор []: 2 перегрузки имеют аналогичные преобразования - PullRequest
3 голосов
/ 13 ноября 2009
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;
}

Ответы [ 8 ]

12 голосов
/ 13 ноября 2009

Если вы прочитаете оставшуюся часть сообщения об ошибке (в окне вывода), оно станет немного понятнее:

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>        ]

Компилятор не может решить, использовать ли ваш перегруженный operator[] или встроенный operator[] на const T*, который он может получить с помощью следующей функции преобразования:

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

Оба следующих варианта являются потенциально допустимыми интерпретациями оскорбительных строк:

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

Вы можете устранить неоднозначность, явно приведя целочисленные индексы к беззнаковым, так что преобразование не требуется:

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

или путем замены перегруженных operator[] s на int вместо unsigned int, или путем удаления operator T*() const (и, возможно, неконстантной версии для полноты).

5 голосов
/ 13 ноября 2009

Проще говоря: компилятор не знает, нужно ли преобразовать v в const float*, а затем использовать operator[] для указателя или преобразовать 0 в unsigned int, а затем использовать operator[] за const v3.

Исправлено, вероятно, удаление operator[]. Я не могу думать ни о чем, что это дает вам, что оператор преобразования в T * еще не сделал. Если бы вы планировали поместить некоторую проверку границ в operator[], то я бы сказал, заменить операторы преобразования функциями getPointer (поскольку в общем случае вы не хотите неявно преобразовывать безопасную вещь в небезопасную вещь), или делать то, что делает std::vector, то есть пользователи получают указатель с &v[0].

Еще одно изменение, которое позволяет компилировать, - это изменить operator[] на параметр int вместо unsigned int. Затем в вашем коде компилятор однозначно выбирает интерпретацию без преобразования. Согласно моему компилятору, все еще нет никакой двусмысленности даже при использовании беззнакового индекса. Что приятно.

3 голосов
/ 13 ноября 2009

Когда вы компилятор компилирует следующее

v[0]

это должно учитывать две возможные интерпретации

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

Ни один из кандидатов не лучше другого, потому что каждый из них требует преобразования. Первый вариант требует пользовательского преобразования из v3<T> в T*. Второй вариант требует стандартного преобразования из int (0 - int) в unsigned int, поскольку перегруженный [] требует аргумента unsigned int. Это делает этих кандидатов несопоставимыми (ни один из них явно не лучше по правилам C ++) и, следовательно, делает вызов неоднозначным.

Если вы вызываете оператора как

v[0U]

неоднозначность исчезнет (поскольку 0U уже является unsigned int) и ваш перегруженный [] будет выбран. Кроме того, вы можете объявить ваш перегруженный [] с аргументом int. Или вы можете полностью удалить оператор преобразования. Или сделайте что-нибудь еще, чтобы устранить двусмысленность - вы решаете.

2 голосов
/ 13 ноября 2009

Помните, что класс - друг сам по себе:

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

Когда вы копируете что-то того же типа, вы уже знакомы с деталями реализации. Таким образом, нет проблем с непосредственным доступом к реализации, если это уместно. Таким образом, из конструктора вы можете получить доступ к объекту, который копируете напрямую, и увидеть его член '_a'.

Если вы хотите узнать исходную проблему:

Литерал '1' в контексте 'v [1]' является целым числом (это синоним целого числа со знаком). Таким образом, чтобы использовать оператор [], технически компилятор должен вставить преобразование из int в unisgned. Другая альтернатива - использовать оператор * (), чтобы получить указатель на внутренний объект, а затем использовать оператор [] для указателя. Компилятору не разрешается делать этот выбор и выдавать ошибку:

Параметры компилятора:

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

Чтобы сделать его невидимым, вы можете использовать беззнаковый литерал;

 _a[1] = v[1u];

В долгосрочной перспективе, возможно, стоит сделать это проще для пользователя.
Преобразуйте оператор [], чтобы использовать int вместо unsigned int, тогда вы получите точное совпадение, когда целочисленные литералы (или вы можете иметь два набора оператора []. Один использует int, а другой использует unsigned int).

2 голосов
/ 13 ноября 2009

Виновным является ваш оператор преобразования типов. v преобразован в указатель с плавающей точкой. Теперь возможны два оператора [], один из которых является встроенным оператором индексации для float, а другой - тот, который вы определили для v, который следует выбрать для языка, так что это неоднозначность согласно ISO.

1 голос
/ 13 ноября 2009

Я не видел его, пока Джеймс МакНеллис не опубликовал полное сообщение об ошибке, но неоднозначность не между двумя v3::operator[]() функциями, как кажется.

Вместо этого, поскольку нет точного соответствия между типами аргументов, компилятор не может решить, следует ли:

a) Используйте v3::operator[](unsigned int) const, тем самым преобразуя аргумент int в unsigned, или

b) Используйте преобразование v3::operator const T*() const, за которым следует встроенный оператор индексации массива.

Этого можно избежать, если оператор [] аргументирует int, а не unsigned int. Но лучшим решением было бы избежать неявного преобразования в T * и вместо этого предоставить функцию Data (), которая делала это явно.

0 голосов
/ 31 января 2018

У меня была такая же проблема: я решил ее, просто сделав оператор typecast явным.

0 голосов
/ 13 ноября 2009

Версия const ничего не меняет. Версия, отличная от const, позволяет назначать объекты с помощью записи массива (v[3] = 0.5;).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...