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

Приоритет функций шаблона С++

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, const T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    double g = 2.;
    foo(a, g); // prints "first"

    return 0;
}

Чтобы вызвать вторую перегрузку foo, компилятору необходимо выполнить только один вывод типа шаблона, но для первой перегрузки ему необходимо выполнить два. Не могли бы вы объяснить, почему называется первая перегрузка?

4b9b3361

Ответ 1

Разрешение перегрузки выполняется в несколько этапов.

Во-первых, путем поиска имени мы выбираем список жизнеспособных кандидатов. В этом случае это:

template <class U, class T>
void foo(U&, T&);            // with U = int, T = double

template <class T>
void foo(int&, const T&)     // with T = double

Затем мы определяем последовательность преобразования, необходимую для каждого аргумента для каждого жизнеспособного кандидата. Это [over.ics.rank]:

Стандартная последовательность преобразования S1 является лучшей последовательностью преобразования, чем стандартная последовательность преобразования S2, если [...]

  • S1 - собственная подпоследовательность S2 (сравнение последовательностей преобразования в канонической форме определенном в соответствии с пунктом 13.3.3.1.1, за исключением любого преобразования Lvalue; последовательность преобразования идентичности рассматривается как подпоследовательность любой последовательности, не являющейся идентификационной конверсией) или, если не это,
  • ранг S1 лучше ранга S2, или S1 и S2 имеют одинаковый ранг и различаются по правилам в параграфе ниже, или, если не это,

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

  • S1 и S2 являются привязками привязки (8.5.3), и ни один из них не ссылается на неявный параметр объекта нестатическая функция-член, объявленная без реф-квалификатора, и S1 связывает ссылку rvalue с rvalue и S2 связывают ссылку lvalue.

Ненужные.

  • S1 и S2 являются привязками привязки (8.5.3), а S1 связывает ссылку lvalue с функцией lvalue и S2 связывает ссылку rvalue с функцией lvalue.

Неа.

  • S1 и S2 отличаются только их квалификационным преобразованием и дают аналогичные типы T1 и T2 (4.4), соответственно, а cv-квалификационная сигнатура типа T1 является собственным подмножеством cv-квалификации подпись типа T2.

Квалификационное преобразование - вещь указателя, нет.

  • S1 и S2 являются привязками привязки (8.5.3), а типы, к которым относятся ссылки, одинаковы тип, за исключением cv-квалификаторов верхнего уровня, и тип, к которому относится ссылка, инициализированная S2 является более cv-квалифицированным, чем тип, к которому относится ссылка, инициализированная S1.

В этом случае первая перегрузка принимает второй аргумент как double&, а вторая перегрузка занимает const double&. Первый из них менее квалифицирован, чем последний, поэтому мы останавливаемся здесь - предпочитаем foo(U&,T&).

Только после шагов, чтобы определить, какая последовательность преобразований лучше, мы переходим к шагу, который предпочтительнее более специализированный шаблон. Полный порядок правил в [over.match.best]:

Учитывая эти определения, жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументов i, ICSi (F1) не является худшей последовательностью преобразования, чем ICSi (F2), а затем

  • для некоторого аргумента j, ICSj (F1) является лучшей последовательностью преобразования, чем ICSj (F2), или, если не это,

Это то, что мы только что прошли.

  • контекст - это инициализация по пользовательскому преобразованию [...]
  • контекст - это инициализация функцией преобразования для привязки прямых ссылок [...]
  • F1 не является специализированной функцией шаблона, а F2 является специализированной функцией шаблона, или, если это не так,
  • F1 и F2 - специализированные шаблоны функций, а шаблон функции для F1 - более специализированный чем шаблон для F2 в соответствии с правилами частичного упорядочения, описанными в 14.5.6.2.

Вот почему мы выбираем foo(U&, T&). Однако, если вы удалите const, то обе последовательности преобразования идентичны для всех шагов - поэтому в этот момент выиграет более специализированный шаблон (foo(int&, T&)).

Обратите внимание, что более специализированным является механизм очень последний, чтобы определить лучшего кандидата. Это самый последний из таймеров.

Также обратите внимание, что количество вычетов шаблонов не имеет значения. Это может иметь значение при выборе между перегрузкой, которая является шаблоном, и перегрузкой, которая не является шаблоном, - но при выборе между перегрузкой, имеющей параметры шаблона x, и перегрузкой, имеющей параметры шаблона y > x, не имеет значения.

Ответ 2

Вы объявляете во второй функции, что второй аргумент должен быть const. Приведенный ниже пример с прилагаемой коррекцией вызывает вторую:

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    double g = 2.;
    foo(a, g);

    return 0;
}

С другой стороны, когда вы явно объявляете второй аргумент, const в main(), приложение вызывает вторую функцию в вашем предыдущем примере, как ожидалось:

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, const T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    const double g = 2.;
    foo(a, g);

    return 0;
}

Ответ 3

Заметим:

  • Первая перегрузка принимает значение non-const lvalue как второй аргумент
  • Вторая перегрузка принимает значение const lvalue как второй аргумент

Поскольку вы передаете g как не константу lvalue, компилятор выбирает первую перегрузку.