Приоритет и константа преобразования шаблонов
У меня есть что-то вроде:
#include <iostream>
class Foo;
struct Test
{
template <typename T>
operator T() const // <----- This const is what puzzles me
{
std::cout << "Template conversion" << std::endl;
return T{};
}
operator Foo*()
{
std::cout << "Pointer conversion" << std::endl;
return nullptr;
}
};
int main()
{
Test t;
if (t)
{
std::cout << "ahoy" << std::endl;
}
bool b = (bool)t;
Foo* f = (Foo*)t;
}
Он строит отлично, но когда я запускаю его, в то время как я ожидаю получить
$> ./a.out
Template conversion
Template conversion
Pointer conversion
Вместо этого я получаю
$> ./a.out
Pointer conversion
Pointer conversion
Pointer conversion
Если я удалю const или создаю экземпляр Test const, то все будет работать так, как ожидалось.
Точнее, выбор перегрузки, по-видимому, имеет смысл, когда оба оператора имеют одинаковую квалификацию const.
13.3.3.1.2 точка стандарта заставляет меня думать, что я должен получить преобразование идентичности, конвертируя в bool, используя экземпляр оператора преобразования шаблона с помощью T
= bool
, хотя, очевидно, скрывается скрытность где-то. Может ли кто-нибудь просветить меня о том, какое правило здесь играет здесь?
Ответы
Ответ 1
При сравнении последовательностей преобразования перед преобразованием типа результата учитываются преобразования параметров. Параметр неявный объект (указатель this
) рассматривается как параметр, а преобразование квалификации (Foo -> Foo const
) хуже, чем преобразование идентификатора в неявном параметре объекта. От [over.match.best]:
1 - [...] жизнеспособная функция F1 определена как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументов i, ICSi (F1) не является худшей последовательностью преобразования, чем ICSi (F2), а затем
- для некоторого аргумента j ICSj (F1) является лучшей последовательностью преобразования, чем ICSj (F2), или, если не это, [...]
Таким образом, оператор преобразования без const
-qualified-члена всегда будет лучше, чем a const
-qualified, даже если преобразование результата является точным на последнем.
Ответ 2
Соответствующие правила определены в [over.match.best]:
Учитывая эти определения, жизнеспособная функция F1
определяется как лучшая функция, чем другая жизнеспособная функция F2
, если для всех аргументов i, ICSi (F1
) не хуже схемы преобразования, чем ICSi (F2
), а затем (1.3) - для некоторого аргумента j, ICSj (F1
) является лучшей последовательностью преобразования, чем ICSj (F2
), или, если не это,
(1.4) - контекст представляет собой инициализацию путем пользовательского преобразования (см. 8.5, 13.3.1.5 и 13.3.1.6) и стандартную последовательность преобразования из возвращаемого типа F1
в тип назначения (т.е. Тип инициализация объекта) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования из возвращаемого типа F2
в тип назначения.
Позвольте просто взглянуть на первый случай bool
. У нас есть два жизнеспособных кандидата:
Test::operator T<bool>() const;
Test::operator Foo*();
Оба вызываются с не-t210 > Test
. Для второй перегрузки конверсии не нужны - последовательность преобразования - это просто точное соответствие. Однако для первой перегрузки неявный аргумент this
должен пройти квалификационное преобразование от Test
до const Test
. Таким образом, предпочтительна вторая перегрузка - мы не переходим ко второму этапу, в котором обсуждается тип возврата.
Если мы опустим const
, то жизнеспособные кандидаты станут:
Test::operator T<bool>();
Test::operator Foo*();
Здесь оба кандидата одинаково жизнеспособны с идентичными последовательностями преобразования, но шаблон bool
является предпочтительным, поскольку последовательность преобразования из возвращаемого типа bool
в bool
(Identity - самый высокий ранг) является лучшей последовательностью преобразования чем от Foo*
до bool
(Boolean Conversion - самый низкий).