Подтвердить что ты не робот

Должна ли эта компиляция? Разрешение перегрузки и неявные преобразования

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

EDIT: Р. Мартиньо Фернандес попробовал это на 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;
}

Но clang жалуется:

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]) (после того, как пользовательское преобразование и стандартное преобразование удвоятся дольше).

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

EDIT: (разъяснение вопросов)

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

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

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

4b9b3361

Ответ 1

Лучше понять, почему разрешение перегрузки неоднозначно, пройдя его шаг за шагом.

§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) следующим образом:

  • if F - статическая функция-член, [...]; в противном случае,
  • let ICSi(F) обозначает неявную последовательность преобразований, которая преобразует i -th аргумент в список к типу 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) является лучшей последовательностью преобразования, чем пользовательская последовательность преобразования или последовательность преобразования многоточия.

So 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 Если существует только одна жизнеспособная функция, которая является лучшей функцией, чем все другие жизнеспособные функции, то она выбирается с помощью разрешения перегрузки; иначе звонок плохо сформирован.

Хорошо, f * ck.:) Таким образом, Clang правильно отклоняет этот код.

Ответ 2

Кажется, нет никаких сомнений в том, что как 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, если для всех аргументов я ICSi (F1) не является худшей последовательностью преобразования, чем ICSi (F2),...

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

Ответ 3

Как видно из 13.6 в спецификации С++ 11, clang здесь корректен:

13,6 Встроенные операторы [Over.built]

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

  
    

:

    

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

Т & оператор [] (T *, std:: ptrdiff_t);

     

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

изменить

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

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

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

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

Эта последняя точка является способным способом обхода - add:

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

в класс Something