С++ error: operator []: 2 перегрузки имеют аналогичные преобразования

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;
}

Ответ 1

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

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[], чтобы взять int вместо unsigned int или удалив operator T*() const (и, возможно, неконстантную версию тоже, для полноты).

Ответ 2

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

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

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

Ответ 3

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

v[0]

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

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

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

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

v[0U]

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

Ответ 4

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

Ответ 5

Помните, что класс является другом:

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).

Ответ 6

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

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

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

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

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

Ответ 7

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

Ответ 8

Версия const ничего не изменяет. Версия non const позволяет назначать вещи с использованием нотации массива (v[3] = 0.5;).