Ответ 1
Здесь действуют несколько сил. Чтобы понять, что происходит, давайте рассмотрим, куда (Foo)x
должен нас привести. Прежде всего, это приведение в стиле c эквивалентно static_cast
в данном конкретном случае. И семантика статического приведения будет заключаться в прямой инициализации объекта результата. Поскольку результирующий объект будет иметь тип класса, [dcl.init]/17.6.2 сообщает нам, что он инициализирован следующим образом:
В противном случае, если инициализация является прямой инициализацией, или если это инициализация копирования, где cv-неквалифицированная версия исходного типа является тем же классом, или производным классом, класса назначения, рассматриваются конструкторы. Применимые конструкторы перечисляются ([over.match.ctor]), и лучший выбирается через разрешение перегрузки. Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора или списком выражений в качестве аргументов. Если конструктор не применяется, или разрешение перегрузки неоднозначно, инициализация некорректна.
Так что перегрузите разрешение, чтобы выбрать конструктор Foo
для вызова. А если не удается разрешить перегрузку, программа работает некорректно. В этом случае он не должен потерпеть неудачу, даже если у нас есть 3 конструктора-кандидата. Это Foo(int)
, Foo(Foo const&)
и Foo(Foo&&)
.
Во-первых, нам нужно скопировать инициализировать int
как аргумент в конструктор, а это значит найти неявную последовательность преобразования из Bar<char>
в int
. Поскольку определяемый пользователем оператор преобразования, указанный вами из Bar<char>
в char
, не является явным, мы можем использовать его для неявной последовательности разговоров Bar<char> → char → int
.
Для двух других конструкторов нам нужно привязать ссылку к Foo
. Однако мы не можем этого сделать. Согласно [over.match.ref]/1:
При условиях, указанных в [dcl.init.ref], ссылка может быть напрямую связана с glvalue или классом prvalue, который является результатом применения функции преобразования к выражению инициализатора. Разрешение перегрузки используется для выбора функции преобразования, которая будет вызвана. Предполагая, что "cv1 T" является базовым типом инициализируемой ссылки, а "cv S" является типом выражения инициализатора, с S типом класса, функции-кандидаты выбираются следующим образом:
- Рассматриваются функции преобразования S и его базовые классы. Те неявные функции преобразования, которые не скрыты в S и дают тип "lvalue ссылка на cv2 T2" (при инициализации lvalue ссылка или rvalue ссылка на функцию) или "cv2 T2" или "rvalue ссылка на cv2 T2" (когда инициализация ссылки rvalue или ссылки lvalue на функцию), где "cv1 T" является совместимым со ссылкой ([dcl.init.ref]) с "cv2 T2", являются функциями-кандидатами. Для прямой инициализации те явные функции преобразования, которые не скрыты в S и дают тип "lvalue ссылка на cv2 T2" или "cv2 T2" или "rvalue ссылка на cv2 T2", соответственно, где T2 - тот же тип, что и T или могут быть преобразованы в тип T с преобразованием квалификации ([conv.qual]), также являются функциями-кандидатами.
Единственная функция преобразования, которая может дать нам glvalue или prvalue типа Foo
- это специализация указанного вами явного шаблона функции преобразования. Но, поскольку инициализация аргументов функции не является прямой инициализацией, мы не можем рассмотреть явную функцию преобразования. Поэтому мы не можем вызывать копирование или перемещать конструкторы в разрешении перегрузки. Это оставляет нас только с конструктором, принимающим int
. Таким образом, разрешение перегрузки является успешным, и это должно быть так.
Тогда почему некоторые компиляторы считают это неоднозначным или вместо этого вызывают оператор шаблонного преобразования? Итак, поскольку в стандарт было введено гарантированное разрешение копирования, было отмечено (CWG выпуск 2327), что определяемые пользователем функции преобразования также должны способствовать разрешению копирования. Сегодня, согласно сухому письму стандарта, они этого не делают. Но мы бы очень хотели, чтобы они. Хотя формулировка того, как именно это должно быть сделано, все еще разрабатывается, может показаться, что некоторые компиляторы уже пытаются это реализовать.
И это та реализация, которую вы видите. Это противодействующая сила расширения копий, которая препятствует разрешению перегрузки здесь.