Ответ 1
Прежде всего: при преобразовании выражения в оператор return
в возвращаемый тип функции правила такие же, как для инициализации (см. [conv]/2.4 и [conv]/3).
Таким образом, мы могли бы сравнить поведение кода с использованием этого примера (с тем же X
, что и у вас, но без Y
):
X test1;
bool b1 = test1;
X const test2;
bool b2 = test2;
(в вызове y.test2()
тип this->x
равен X const
, что означает наличие функции const-члена). Это также было бы одинаково, если мы вместо bool
будем писать .
Часть Стандарта, касающаяся разрешения перегрузки в этой ситуации, - [over.match.conv], вот текст С++ 14 (с некоторым исключением для краткости):
13.3.1.5 Инициализация с помощью функции преобразования [over.match.conv]
1 В условиях, указанных в 8.5, как часть инициализации объекта типа nonclass, можно вызвать функцию преобразования, чтобы преобразовать выражение инициализатора типа класса в тип объекта инициализируется. [...]
2 Список аргументов имеет один аргумент, который является выражением инициализатора. [Примечание. Этот аргумент будет сравниваться с неявным параметром объекта функций преобразования. -end note]
Случай test2
прост - функция-не-const-член не может быть вызвана на const-объект, поэтому operator void*
никогда не рассматривается, существует только один кандидат и нет необходимости в разрешении перегрузки. Вызывается operator bool()
.
Итак, для остальной части этого сообщения я просто расскажу о случае test1
. Часть I, приведенная в приведенной выше цитате, охватывает, что как operator bool
, так и operator void*()
являются кандидатными функциями для случая test1
.
Важно отметить, что разрешение перегрузки выбирает среди этих двух функций-кандидатов, и это не случай рассмотрения двух неявных последовательностей преобразования, каждый из которых содержит пользовательский код, определенного преобразования. Чтобы поддержать это, просмотрите первое предложение [over.best.ics]:
Неявная последовательность преобразования представляет собой последовательность преобразований, используемых для преобразования аргумента в вызов функции к типу соответствующего параметра вызываемой функции.
Мы не конвертируем аргумент в вызов функции здесь. Правила о неявных последовательностях преобразования вступают в игру, когда мы оцениваем кандидатские функции: правила применяются к каждому аргументу каждой кандидатской функции, как мы увидим через мгновение.
Итак, теперь мы рассмотрим правила для лучшей жизнеспособной функции, чтобы определить, какая из этих двух кандидатных функций выбрана. Я пропущу [over.match.viable], в котором уточняется, что оба этих кандидата жизнеспособны и на [over.match.best].
Ключевой частью этого раздела является [over.match.best]/1.2:
пусть ICSi (F) обозначает неявную последовательность преобразований, которая преобразует i-й аргумент в список в тип i-го параметра жизнеспособной функции F. 13.3.3.1 определяет последовательности неявного преобразования и 13.3.3.2 определяет что означает, что одна неявная последовательность преобразований является лучшей последовательностью преобразования или хуже, чем другая.
Здесь i == 1
существует только один аргумент test1
, как описано выше [over.match.conv]/2 - аргумент является выражением инициализатора test1
; "параметр жизнеспособной функции" является неявным параметром объекта функций-членов X
.
Теперь применяются правила неявной последовательности преобразования:
-
operator void*()
не требует преобразования - аргументX
, а параметрX&
-
operator bool() const
требуется преобразование квалификации - аргументX
, а параметрX const&
Конверсия не лучше конверсии ([over.ics.rank]/3.1.1). Таким образом, ICS1 (operator void*()
) является лучшей последовательностью преобразования, чем ICS1 (operator bool() const
); поэтому в этот момент operator void*()
выигрывает ([over.match.best]/1.3).
В следующем параграфе [over.match.best]/1.4 объясняется, что произошло бы, если бы ни одна из этих двух последовательностей не была лучше: мы только затем продолжим сравнивать стандартные последовательности преобразования из возвращаемого типа функции-кандидата на тип инициализируется.
Вы можете изучить этот случай, изменив член X
на operator void*() const
. Теперь две последовательности ICS1 неотличимы от /1.3, поэтому мы переходим к /1.4, в которые выигрывает точка operator bool() const
, потому что ее преобразование в bool
является идентификатором, тогда как operator void*() const
все еще требует булевского преобразования.