Я упростил ваш пример до следующего:
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.