Перегрузка оператора C++ – приведение из класса
-
12-09-2019 - |
Вопрос
При переносе кода 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()
"удаляет ошибку.)
Решение
Он компилируется правильно, если вы удалите приведение, и я проверил, что оператор 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;
}
Другие советы
Двусмысленность возникает из-за интерпретации актерское выражение.
При выборе преобразования компилятор сначала учитывает 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>
придется создать, и это делает этот вариант худшей последовательностью преобразования, чем первый вариант.
Я упростил ваш пример до следующего:
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);"
хорошо сформировано, для некоторой изобретенной временной переменной T (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».
Надеюсь, это имеет какой-то смысл!
ОБНОВЛЯТЬ:
Здесь есть два типа синтаксиса инициализации:
UCList t1 (mc);
UCList t2 = mc;
't1' - это прямая инициализация (13.3.1.3) и все конструкторы включены в набор перегрузки.Это почти похоже на наличие нескольких пользовательских преобразований.Есть набор конструкторов и набор операторов преобразования.(т.е.м:н).
В случае «t2» синтаксис использует инициализацию копирования (13.3.1.4) и другие правила:
В условиях, указанных в 8.5, как часть копирования инициализации объекта типа класса, можно вызвать пользовательское преобразование для преобразования выражения инициализатора в тип инициализированного объекта.Разрешение перегрузки используется для выбора определяемого пользователем преобразования, которое будет вызвано.
В этом случае нужно ввести только один, UCList
, поэтому необходимо учитывать только набор перегрузок операторов преобразования, т.е.мы не рассматриваем другие конструкторы UCList.