Это должно скомпилироваться?Разрешение перегрузки и неявные преобразования - PullRequest
18 голосов
/ 18 января 2012

Этот пример, кажется, компилируется с VC10 и gcc (хотя моя версия gcc очень старая).

РЕДАКТИРОВАТЬ: Р. Мартиньо Фернандес попробовал это на gcc 4.7, и поведение остается тем же.

struct Base
{
    operator double() const { return 0.0; }
};

struct foo
{
    foo(const char* c) {}
};

struct Something : public Base
{
    void operator[](const foo& f) {}
};

int main()
{
    Something d;
    d["32"];

    return 0;
}

Но лязг жалуется:

test4.cpp:19:6: error: use of overloaded operator '[]' is ambiguous (with operand types 'Something' and 'const char [3]')
    d["32"]
    ~^~~~~
test4.cpp:13:10: note: candidate function
    void operator[](const foo& f) {}
         ^
test4.cpp:19:6: note: built-in candidate operator[](long, const char *)
    d["32"]
     ^
test4.cpp:19:6: note: built-in candidate operator[](long, const restrict char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile restrict char *)

Разрешение перегрузки рассматривает две возможные функции из этого выражения:

  • вызов Something :: operator [] (после преобразования, определенного пользователем)
  • вызов встроенного оператора для const char * (подумайте «32» [d]) (после преобразования, определенного пользователем, и стандартного преобразования double в long).

Если бы я написал d["32"] как d.operator[]("32"), то разрешение перегрузки даже не будет смотреться на вариант 2, и clang также скомпилирует нормально.

РЕДАКТИРОВАТЬ: (уточнение вопросов)

Это, кажется, сложная область в разрешении перегрузки, и поэтому я был бы очень признателен за ответы, которые подробно объясняют разрешение перегрузки в этом случае и цитируют стандарт (если есть некоторые неясные / продвинутые, которые могут неизвестное правило).

Если clang верен, мне также интересно узнать, почему эти двое неоднозначны / один не предпочтительнее другого. Вероятно, ответ должен был бы объяснить, как при разрешении перегрузки учитываются неявные преобразования (как определяемые пользователем, так и стандартные преобразования), связанные с двумя кандидатами, и почему одно не лучше другого.

Примечание: если оператор double () изменяется на оператор bool (), все три (clang, vc, gcc) откажутся компилировать с аналогичной неоднозначной ошибкой.

Ответы [ 3 ]

12 голосов
/ 19 января 2012

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

§13.5.5 [over.sub]

Таким образом, выражение подписки x[y] интерпретируется как x.operator[](y) для объекта класса x типа T, если существует T::operator[](T1), и , если оператор выбран в качестве функции наилучшего соответствия с помощью механизма разрешения перегрузки (13.3.3) .

Теперь нам сначала понадобится набор перегрузки.Он построен в соответствии с §13.3.1 и содержит как член, так и функции, не являющиеся членами.См. этот мой ответ для более подробного объяснения.

§13.3.1 [over.match.funcs]

p2 Набор функций-кандидатов может содержать функции-члены и не-членыбыть решенным против того же списка аргументов.Так что списки аргументов и параметров сравнимы в этом гетерогенном наборе, считается, что функция-член имеет дополнительный параметр, называемый параметром неявного объекта, который представляет объект, для которого функция-член была вызвана .[...]

p3 Точно так же, когда это уместно, контекст может создать список аргументов, который содержит подразумеваемый аргумент объекта для обозначения объекта, с которым нужно работать.

// abstract overload set (return types omitted since irrelevant)
f1(Something&, foo const&); // linked to Something::operator[](foo const&)
f2(std::ptrdiff_t, char const*); // linked to operator[](std::ptrdiff_t, char const*)
f3(char const*, std::ptrdiff_t); // linked to operator[](char const*, std::ptrdiff_t)

Затем создается список аргументов:

// abstract argument list
(Something&, char const[3]) // 'Something&' is the implied object argument

И затем список аргументов проверяется для каждого члена набора перегрузки:

f1 -> identity match on argument 1, conversion required for argument 2
f2 -> conversion required for argument 1, conversion required for argument 2 (decay)
f3 -> argument 1 incompatible, argument 2 incompatible, discarded

Затем,поскольку мы выяснили, что требуются неявные преобразования, мы рассмотрим §13.3.3 [over.match.best] p1:

Определим ICSi(F) следующим образом:

  • , если Fстатическая функция-член, [...];в противном случае
  • let ICSi(F) обозначает последовательность неявного преобразования, которая преобразует i -ый аргумент в списке в тип i -ого параметра жизнеспособной функции F.13.3.3.1 определяет последовательности неявного преобразования, а 13.3.3.2 определяет, что означает, что одна последовательность неявного преобразования является лучшей последовательностью преобразования или худшей последовательностью преобразования, чем другая.

Теперь давайте построим этипоследовательности неявного преобразования для f1 и f2 в наборе перегрузки (§13.3.3.1):

ICS1(f1): 'Something&' -> 'Someting&', standard conversion sequence
ICS2(f1): 'char const[3]' -> 'foo const&', user-defined conversion sequence
ICS1(f2): 'Something&' -> 'std::ptrdiff_t', user-defined conversion sequence
ICS2(f2): 'char const[3]' -> 'char const*', standard conversion sequence

§13.3.3.2 [over.ics.rank] p2

стандартная последовательность преобразования (13.3.3.1.1) является лучшей последовательностью преобразования, чем определяемая пользователем последовательность преобразования или последовательность преобразования многоточия.

Так что ICS1(f1) лучше, чем ICS1(f2), а ICS2(f1) хуже, чем ICS2(f2).
И наоборот, ICS1(f2) хуже ICS1(f1), а ICS2(f2) лучше ICS2(f1).

§13.3.3 [over.match.best]

p1 (продолжение)В этих определениях жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2 , если для всех аргументов i, ICSi(F1) не хуже последовательности преобразования, чем ICSi(F2)и затем [...]

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

Ну, ч * ск.:) Таким образом, Clang прав, отвергая этот код.

4 голосов
/ 18 января 2012

Кажется, нет сомнений, что и Something::operator[](const foo& f), и встроенная operator[](long, const char *) являются жизнеспособными функциями-кандидатами (13.3.2) для разрешения перегрузки. Типы реальных аргументов Something и const char*, и последовательности неявного преобразования (ICF), которые я считаю:

  • для Something::operator[](const foo& f): (1-1) преобразование идентичности и (1-2) foo("32") до foo::foo(const char*);
  • для operator[](long, const char *): (2-1) long(double(d)) - Something::operator double() const (унаследовано от Base) и (2-2) преобразование личности.

Теперь, если мы оценим эти ICF в соответствии с (13.3.3.2), мы увидим, что (1-1) - лучшее преобразование, чем (2-1), а (1-2) - худшее преобразование, чем (2 -2). Согласно определению в (13.3.3),

жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2 если для всех аргументов i ICSi (F1) не хуже последовательности преобразования, чем ICSi (F2), ...

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

3 голосов
/ 18 января 2012

Казалось бы, из 13.6 в спецификации C ++ 11 clang здесь верен:

13.6 Встроенные операторы [over.built]

Функции-кандидаты, представляющие встроенные операторы, определенные в разделе 5, определены в этом подпункте.Эти функции-кандидаты участвуют в процессе разрешения перегрузки оператора, как описано в 13.3.1.2, и не используются ни для каких других целей.[Примечание: поскольку встроенные операторы принимают только операнды с типом, не относящимся к классу, и разрешение перегрузки оператора происходит только тогда, когда выражение операнда изначально имеет класс или тип перечисления, разрешение перегрузки оператора может преобразовываться во встроенный оператор, только если операнд имееттип класса, который имеет пользовательское преобразование в неклассовый тип, подходящий для оператора, или когда операнд имеет тип перечисления, который может быть преобразован в тип, подходящий для оператора.Также обратите внимание, что некоторые из функций-кандидатов, приведенных в этом подпункте, являются более разрешающими, чем сами встроенные операторы.Как описано в 13.3.1.2, после того, как встроенный оператор выбран разрешением перегрузки, выражение подчиняется требованиям для встроенного оператора, приведенным в разделе 5, и, следовательно, любым дополнительным семантическим ограничениям, приведенным в нем.Если существует кандидат, написанный пользователем, с тем же именем и типами параметров, что и встроенная операторная функция-кандидат, встроенная операторная функция скрыта и не входит в набор функций-кандидатов.- конец примечания]

:

Для каждого cv-квалифицированного или cv-неквалифицированного типа объекта T существует кандидатоператорные функции вида

T & operator [] (T *, std :: ptrdiff_t);

T & operator [] (std :: ptrdiff_t, T *);

edit

Как только вы выясните, какие операторские функции существуют, это просто становится стандартным разрешением перегрузки, как описано в разделе 13.3 стандарта - около 10страницы подробностей, но суть в том, что для вызова функции не должно быть неоднозначным, должна быть одна функция, которая, по крайней мере, так же хороша, как и все возможные, жизнеспособные функции для каждого аргумента и лучше соответствуетчем другие, по крайней мере, один аргумент.Существует множество деталей о том, что именно означает «лучше», но все сводится к (в данном случае) совпадению, не требующему какого-либо пользовательского оператора преобразования или конструктора объекта, лучше, чем тот, который делает.

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

void Something::operator[](const foo& f)
operator[](long, const char *)

Первое лучше соответствует первому аргументу, а второе - второму.Поэтому, если не существует какой-либо другой функции, которая лучше обоих, ее неоднозначность.

Этот последний пункт является возможным обходным путем - добавьте:

void operator[](const char *a) { return (*this)[foo(a)]; }

к классу Something

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...