Оператор [] (const char *) неоднозначность

Следующий код

#include <string>

struct Foo {
    operator double() {
        return 1;
    }

    int operator[](std::string x) {
        return 1;
    }
};

int main() {
    Foo()["abcd"];
}

Скомпилируется с g++, но с компиляторами clang и intel из-за двусмысленности между объявленным методом и собственным оператором [].

Было бы ясно, если бы Foo имела неявное преобразование в int, но здесь преобразование было double. Разве это не устраняет двусмысленность?

Ответ 1

§13.3.3.1.2 [over.ics.user]/p1-2:

Пользовательская последовательность преобразований состоит из начального стандарта последовательность преобразования, за которой следует пользовательское преобразование (12.3) после чего следует вторая стандартная последовательность преобразования. Если пользовательский преобразование определяется конструктором (12.3.1), начальным стандартная последовательность преобразования преобразует тип источника в тип требуемый аргументом конструктора. Если пользовательский преобразование задается функцией преобразования (12.3.2), начальная стандартная последовательность преобразования преобразует тип источника в неявный параметр объекта функции преобразования.

Вторая стандартная последовательность преобразований преобразует результат пользовательское преобразование в целевой тип для последовательности.

В частности, существует неявное преобразование с плавающей точкой на интегральный тип (§4.9 [conv.fpint]/p1):

Значение типа с плавающей запятой может быть преобразовано в prvalue целочисленный тип. Преобразование усекает; то есть дробная часть отбрасывается. Поведение undefined, если усеченное значение не может быть представлены в типе назначения.

Для целей разрешения перегрузки применимыми кандидатами являются:

Foo::operator[](std::string x)              // overload
operator[](std::ptrdiff_t, const char *);   // built-in

С учетом списка аргументов типов (Foo, const char [5]).

Для соответствия первой функции оператора первый аргумент является точным совпадением; второй требует пользовательского преобразования.

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

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

Обратите внимание, что, хотя для целей разрешения перегрузки оператора пользовательская последовательность преобразования может иметь две стандартные последовательности преобразования (один до и один после пользовательского преобразования), а операнды неклассового типа могут быть преобразованный в соответствии с кандидатами, если выбран встроенный оператор, вторая стандартная последовательность преобразования не применяется для операндов типа класса, и никакое преобразование вообще не применяется для операндов неклассического типа, прежде чем оператор будет интерпретироваться как встроенный (§13.3.1.2 [over.match.oper]/p7):

Если встроенный кандидат выбирается с помощью разрешения перегрузки, операнды типа класса преобразуются в типы соответствующих параметры выбранной функции работы, за исключением того, что вторая стандартная последовательность преобразования пользовательской последовательности преобразования (13.3.3.1.2) не применяется. Тогда оператор рассматривается как соответствующий встроенный оператор и интерпретируется в соответствии с пунктом 5.

Таким образом, если Foo::operator[](std::string x) удаляется, компилятор должен сообщить об ошибке, хотя clang этого не делает. Это очевидная ошибка clang, поскольку не может отклонить пример, приведенный в стандарте.