Приоритет функции const-члена по отношению к возвращаемому значению

В Y::test1() не-const X::operator void*() имеет преимущество перед кажущимся лучшим совпадением, X::operator bool() const - Почему? И где это явление описано в стандарте?

#include <iostream>

struct X {
  operator void*()      { std::cout << "  operator void*()\n"; return nullptr; }
  operator bool() const { std::cout << "  operator bool()\n";  return true; }
};

struct Y {
  X x;
  bool test1()       { std::cout << "test1()\n"; return x; }
  bool test2() const { std::cout << "test2()\n"; return x; }
};

int main() {
  Y y;
  y.test1();
  y.test2();
}

Вывод:

test1()
  operator void*()
test2()
  operator bool()

Ответы

Ответ 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 все еще требует булевского преобразования.

Ответ 2

Неявный this указатель не const. Таким образом, разрешение перегрузки происходит на множестве всех методов не const (включая шаблоны - это один для викторины), прежде чем будут рассмотрены методы const.

Тот факт, что функция помечена const, никогда не делает ее лучше подходящей для указателя не const this, если вы получите мой смысл.

См. http://en.cppreference.com/w/cpp/language/overload_resolution, который в значительной степени поддерживает стандарты.