Почему пользовательское преобразование лучше, чем стандартное целочисленное преобразование?

0

Вы можете найти текст, приведенный ниже в Приложении B к книге "Шаблоны C++" Полное руководство "Дэвида Вандевоорда и Николаса Йосуттиса.

B.2 Упрощенное разрешение перегрузки

Учитывая этот первый принцип, нам остается указать, насколько хорошо данный аргумент соответствует соответствующему параметру жизнеспособного кандидата. В первом приближении мы можем ранжировать возможные совпадения следующим образом (от лучшего к худшему):

  • Идеальное совпадение. Параметр имеет тип выражения или имеет тип, который является ссылкой на тип выражения (возможно, с добавленными константными и/или летучими квалификаторами).
  • Сопоставьте с незначительными корректировками. Это включает в себя, например, распад переменной массива указателю на его первый элемент или добавление const для сопоставления аргументу типа int ** с параметром типа int const * const *.
  • Сопоставьте с продвижением. Продвижение - это своего рода неявное преобразование, которое включает преобразование небольших интегральных типов (таких как bool, char, short и иногда перечисления) в int, unsigned int, long или unsigned long и преобразование float в double.
  • Совпадение только со стандартными преобразованиями. Это включает любое стандартное преобразование (например, int to float), но исключает неявный вызов оператору преобразования или конструктору преобразования.
  • Сопоставьте с пользовательскими преобразованиями. Это допускает любое неявное преобразование.
  • Совпадение с многоточием. Параметр эллипсиса может соответствовать почти любому типу (но не типы POD-типов приводят к неопределенному поведению).

Несколько страниц позже в книге показан следующий пример и текст (акцент мой):

class BadString {
    public:
    BadString(char const*);
    ...
    // character access through subscripting:
    char& operator[] (size_t); // (1)
    char const& operator[] (size_t) const;
    // implicit conversion to null-terminated byte string:
    operator char* (); // (2)
    operator char const* ();
    ...
};
int main()
{
    BadString str("correkt");
    str[5] = 'c'; // possibly an overload resolution ambiguity!
}

Во-первых, ничто не кажется двусмысленным в отношении выражения str [5]. Оператор индекса в (1) кажется идеальным совпадением. Однако это не совсем идеально, потому что аргумент 5 имеет тип int, и оператор ожидает целое число без знака (size_t и std :: size_t обычно имеют тип unsigned int или unsigned long, но никогда не вводят int). Тем не менее, простое стандартное целочисленное преобразование делает (1) легко жизнеспособным. Однако есть еще один жизнеспособный кандидат: встроенный оператор нижнего индекса. Действительно, если мы применяем оператор неявного преобразования к str (который является неявным аргументом функции-члена), мы получаем тип указателя, и теперь применяется встроенный оператор индекса. Этот встроенный оператор принимает аргумент типа ptrdiff_t, который на многих платформах эквивалентен int и, следовательно, является идеальным совпадением для аргумента 5. Таким образом, хотя встроенный оператор нижнего индекса является плохой совпадением (путем пользовательского преобразования ) для подразумеваемого аргумента, это лучшее совпадение, чем оператор, определенный в (1) для реального индекса! Отсюда и потенциальная двусмысленность.

  • 1
    Что ты пытаешься спросить?
Теги:
templates
overload-resolution

2 ответа

0
str.operator[](size_t(5));

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

0

Обратите внимание, что первый список - это упрощенное разрешение перегрузки.

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

str[(ptrdiff_t)5] = 'c'; // definitely an overload resolution ambiguity!

Теперь я получаю сообщение от g++:

warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: [enabled by default]

и --pedantic-errors способствует предупреждению об ошибке.

Поэтому, не погружаясь в стандарт, это говорит о том, что упрощенный список является лишь частью истории. В этом списке вы узнаете, какой из нескольких возможных маршрутов предпочитаете при переходе от A к B, но он не говорит вам, предпочитаете ли вы поездку от A до B в поездке с C на D.

Вы можете увидеть одно и то же явление (и то же g++ сообщение) более очевидно здесь:

struct S {
    explicit S(int) {}
    operator int() { return 0; }
};

void foo(const S&, long) { }
void foo(int, int) { }

int main() {
    S s(0);
    foo(s, 1);
}

Опять же вызов foo неоднозначен, потому что, когда у нас есть выбор того, какой аргумент для неявного преобразования, чтобы выбрать перегрузку, правила не говорят: "выберите более легкое преобразование двух и преобразуйте аргумент, требующий этого преобразования ".

Теперь вы спросите меня о стандартной цитате, но я буду использовать тот факт, что мне нужно ударить сеном в качестве предлога для того, чтобы не искать его ;-)

Таким образом, здесь неверно, что "преобразованное пользователем преобразование является лучшим совпадением, чем стандартное целочисленное преобразование". Однако в этом случае две возможные перегрузки определяются стандартом одинаково хорошо, и, следовательно, вызов неоднозначен.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню