Подтвердить что ты не робот

Приоритет и константа преобразования шаблонов

У меня есть что-то вроде:

#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, хотя, очевидно, скрывается скрытность где-то. Может ли кто-нибудь просветить меня о том, какое правило здесь играет здесь?

4b9b3361

Ответ 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 - самый низкий).