Перегрузка оператора C ++ - приведение из класса - PullRequest
2 голосов
/ 07 августа 2009

При переносе кода Windows в Linux я обнаружил следующее сообщение об ошибке в GCC 4.2.3. (Да, я знаю, что это небольшая старая версия, но я не могу легко обновить.)

main.cpp:16: error: call of overloaded ‘list(MyClass&)’ is ambiguous
/usr/include/c++/4.2/bits/stl_list.h:495: note: candidates are: std::list<_Tp, _Alloc>::list(const std::list<_Tp, _Alloc>&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]
/usr/include/c++/4.2/bits/stl_list.h:484: note:                 std::list<_Tp, _Alloc>::list(size_t, const _Tp&, const _Alloc&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]

Я использую следующий код для генерации этой ошибки.

#include <list>
class MyClass
    {
    public:
        MyClass(){}

        operator std::list<unsigned char>() const { std::list<unsigned char> a; return a; }
        operator unsigned char() const { unsigned char a; return a; }

    };

    int main()
    {
        MyClass a;
        std::list<unsigned char> b = (std::list<unsigned char>)a;

        return 0;
    }

Кто-нибудь сталкивался с этой ошибкой? Что более важно, как обойти это? (Конечно, можно полностью избежать перегрузки, используя такие функции, как GetChar(), GetList() и т. Д., Но я бы хотел этого избежать.)

(Кстати, удаление «operator unsigned char()» устраняет ошибку.)

Ответы [ 3 ]

6 голосов
/ 07 августа 2009

Неоднозначность происходит от интерпретации выражения выражения .

При выборе преобразования компилятор сначала рассматривает приведение стиля static_cast и рассматривает, как разрешить инициализацию, которая выглядит следующим образом:

std::list<unsigned_char> tmp( a );

Эта конструкция неоднозначна, поскольку a имеет пользовательское преобразование в std::list<unsigned char> и в unsigned char, а std::list<unsigned char> имеет как конструктор, который принимает const std::list<unsigned char>&, так и конструктор, который принимает size_t (к которому можно добавить unsigned char).

При приведении к const std::list<unsigned_char>& эта инициализация считается:

const std::list<unsigned_char>& tmp( a );

В этом случае, когда выбрано пользовательское преобразование в std::list<unsigned_char>, новая ссылка может быть привязана непосредственно к результату преобразования. Если пользовательское преобразование в unsigned char, где было выбрано, необходимо будет создать временный объект типа std::list<unsigned char>, и это делает эту опцию худшей последовательностью преобразования, чем предыдущая.

2 голосов
/ 07 августа 2009

Я упростил ваш пример до следующего:

typedef unsigned int size_t;

template <typename T>
class List
{
public:
  typedef size_t  size_type;
  List (List const &);
  List (size_type i, T const & = T());
};

typedef List<unsigned char> UCList;

class MyClass
{
public:
  operator UCList const () const;
  operator unsigned char () const;
};

void foo ()
{
  MyClass mc;
  (UCList)mc;
}

Во-первых, стандарт определяет, что приведение стиля C должно использовать более подходящее приведение стиля C ++, и в данном случае это static_cast. Таким образом, приведенное выше приведение эквивалентно:

static_cast<UCList> (mc);

Определение static_cast гласит:

Выражение e может быть явно преобразовано в тип T с помощью static_cast вида static_cast<T>(e) если объявление "T t(e);" правильно сформировано, для какой-то придуманной временной переменной т (8,5)

Таким образом, семантика для броска такая же, как для:

UCList tmp (mc);

Из 13.3.1.3 мы получаем набор конструкторов-кандидатов, которые мы можем использовать в UCList:

UCList (UCList const &)              #1
UCList (size_type, T const & = T()); #2

Далее будет два отдельных шага разрешения перегрузки, по одному для каждого оператора преобразования.

Преобразование в # 1: Для целевого типа UCList const & разрешение перегрузки выбирается между следующими операторами преобразования: "operator UCList const ()" и "operator unsigned char ()". Использование unsigned char потребует дополнительного преобразования пользователя и поэтому не является жизнеспособной функцией для этого шага перегрузки. Следовательно, разрешение перегрузки будет успешным и будет использовать operator UCList const ().

Преобразование в # 2: С целевым типом size_t. Аргумент по умолчанию не участвует в разрешении перегрузки. Разрешение перегрузки снова выбирает между операторами преобразования: «operator UCList const ()» и «operator unsigned char ()». На этот раз нет преобразования из UCList в unsigned int, и поэтому эта функция не является жизнеспособной. Значение unsigned char может быть повышено до size_t, поэтому разрешение перегрузки на этот раз будет успешным и будет использоваться "operator UCList const ()".

Но теперь на верхнем уровне есть два отдельных и независимых шага разрешения перегрузки, которые успешно преобразованы из mc в UCList. Результат поэтому неоднозначен.

Чтобы объяснить этот последний момент, этот пример отличается от случая нормального разрешения перегрузки. Обычно между аргументами и типами параметров существует отношение 1: n:

void foo (char);
void foo (short);
void foo (int);

void bar() {
  int i;
  foo (i);
}

Здесь есть i=>char, i=>short и i=>int. Они сравниваются по разрешению перегрузки, и будет выбрана перегрузка int.

В приведенном выше случае мы имеем отношение m: n. Стандарт описывает правила выбора для каждого отдельного аргумента и всех параметров «n», но на этом он заканчивается, но не определяет, как нам следует выбирать между использованием различных аргументов «m».

Надеюсь, в этом есть какой-то смысл!

UPDATE:

Здесь представлены два вида синтаксиса инициализации:

UCList t1 (mc);
UCList t2 = mc;

't1' является прямым инициализацией (13.3.1.3), а все конструкторы включены в набор перегрузки. Это почти как если бы у вас было несколько пользовательских преобразований. Есть набор конструкторов и набор операторов преобразования. (т. е. м: н).

В случае 't2' синтаксис использует инициализацию копирования (13.3.1.4) и другие правила:

В соответствии с условиями, указанными в 8.5, как часть инициализации копирования объекта типа класса, определяется пользователем преобразование может быть вызвано для преобразования выражения инициализатора в тип инициализируемого объекта. Разрешение перегрузки используется для выбора определяемого пользователем преобразования, которое нужно вызвать

В этом случае нужно набрать только один, UCList, и поэтому необходимо учитывать только набор перегрузок операторов преобразования, т.е. мы не рассматриваем другие конструкторы UCList.

1 голос
/ 07 августа 2009

Он правильно компилируется, если вы удалили приведение, и я проверил, выполняется ли оператор std :: list.

int main()
{
    MyClass a;
    std::list<unsigned char> b = a;

    return 0;
}

Или если вы приведете его к константной ссылке.

    int main()
    {
        MyClass a;
        std::list<unsigned char> b = (const std::list<unsigned char>&)a;

        return 0;
     }
...