Я полностью запутался после прочтения вопроса Как сделать эти параметры std :: function однозначными?, до сих пор я думал, что я понял, что частичное упорядочение шаблонов функций Да, но после прочтения этого вопроса я записал три примера, чтобы проверить поведение компилятора, и полученные результаты мне трудно понять.
template <class T>
void foo(T) {}
template <class T>
void foo(T&) {}
int main()
{
int i;
foo<int>(i); // error: call is ambiguous!
}
Вопрос: Обе функции жизнеспособны, это очевидно, но не та, T&
более специализированный, чем T
? Вместо этого компилятор поднимает неоднозначный вызов ошибка.
#include <iostream>
template <class T>
struct X {};
template <>
struct X<int>
{
X() {}
X(X<int&> const&) {} // X<int> is constructible from X<int&>
// note: this is not a copy constructor!
};
template <>
struct X<int&>
{
X() {}
X(X<int> const&) {} // X<int&> is constructible from X<int>
// note: this is not a copy constructor!
};
template <class T>
void bar(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <class T>
void bar(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
int main()
{
bar<int>(X<int>()); // calls void bar(X<T>) [with T = int]
bar<int>(X<int&>()); // calls void bar(X<T&>) [with T = int]
}
Вопрос: Если T&
а также T
неоднозначны в Примере №1, тогда почему здесь ни один вызов не является неоднозначным? X<int>
конструктивно из X<int&>
, так же как X<int&>
конструктивно из X<int>
благодаря предоставленным конструкторам. Это потому, что компилятор сгенерирован X<int>::X(X<int> const&)
Копи-конструктор является лучшая последовательность преобразования чем X<int>::X(X<int&> const&)
(если так, что делает его лучше, обратите внимание, что аргументы передаются по значению), и поэтому порядок специализаций не имеет значения вообще?
#include <iostream>
// note: a new type used in constructors!
template <class U>
struct F {};
template <class T>
struct X
{
X() {}
template <class U>
X(F<U> const&) {} // X<T> is constructible from any F<U>
// note: it takes F type, not X!
};
template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <class T>
void qux(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
int main()
{
qux<int>(F<int>()); // calls void qux(X<T&>) [with T = int]
qux<int>(F<int&>()); // calls void qux(X<T&>) [with T = int]
}
Вопрос: Теперь этот сценарий похож на «соответствие лямбда [](int){}
против std::function<void(int&)>
а также std::function<void(int)>
« от вопроса связано. Почему в обоих звонках более специализированный шаблон функции выбран? Это потому, что последовательность преобразования одинакова, поэтому частичное упорядочение начинает иметь значение?
Все тесты выполнены на GCC 4.9.0 с -std=c++11
и без лишних флагов.
Разрешение перегрузки пытается найти лучшую функцию, подобную этой:
(1) [over.match.best] / 1:
Учитывая эти определения, жизнеспособная функция
F1
определяется как
лучшая функция, чем другая жизнеспособная функцияF2
если для всех аргументов
я,ICSi(F1)
не хуже последовательности преобразования, чемICSi(F2)
, а потом
— для некоторого аргумента J,ICSj(F1)
это лучшее преобразование
последовательность, чемICSj(F2)
или, если не так,
— контекст является
инициализация определенным пользователем преобразованием (см. 8.5, 13.3.1.5 и
13.3.1.6) и стандартную последовательность преобразования из возвращаемого типаF1
к типу назначения (то есть типу сущности, являющейся
инициализировано) — лучшая последовательность преобразования, чем стандартная
последовательность преобразования из возвращаемого типаF2
к месту назначения
тип.
[ Пример:struct A { A(); operator int(); operator double(); } a; int i = a; // a.operator int() followed by no conversion is better // than `a.operator double()` // followed by a conversion to `int` float x = a; // ambiguous: both possibilities require conversions, // and neither is better than the other
— конец примера ] или, если не это,
— F1 не шаблон
функция и F2 — специализация шаблона функции, или, если нет
тот,
— F1 и F2 являются специализациями шаблонов функций, а
Шаблон функции для F1 более специализирован, чем шаблон для F2
в соответствии с правилами частичного заказа, описанными в 14.5.6.2.
но не тот, кто принимает
T&
более специализированный, чемT
?
В соответствии с разрешением перегрузки никакое преобразование не является лучшим (оба являются преобразованиями идентификаторов, которые являются точными совпадениями), и поскольку никакой другой пункт в (1) не применяется, выполняется частичное упорядочение. [temp.deduct.partial] / 5 говорит, что ссылки заменяются типом, на который они ссылаются в целях частичного упорядочения:
Перед выполнением частичного упорядочения
выполняется по типам, используемым для частичного заказа:
— ЕслиP
это
ссылочный тип,P
заменяется указанным типом.
— ЕслиA
это
ссылочный тип,A
заменяется указанным типом.
Поскольку параметры шаблонов параметров полностью идентичны, нетрудно увидеть, что взаимные выводы успешны в обоих направлениях — поэтому ни один шаблон не является более специализированным, чем другой.
Частичный заказ здесь не нужен. Определяемое пользователем преобразование из X<int>
в X<int&>
имеет худший ранг, чем преобразование X<int>
в X<int>
— Последнему присваивается рейтинг «Точное совпадение» [over.ics.user] / 4:
Преобразование выражения типа класса в тот же тип класса
данный ранг Точного Матча, […]
Таким образом, это, очевидно, лучшее преобразование, чем X<int>
в X<int&>
, который имеет конверсионный ранг. То же самое происходит и для X<int&>
в X<int>
,
Третий случай похож на первый. X<int>
а также X<int&>
оба имеют шаблон конструктора, который может принимать произвольную специализацию F
, (1) говорит нам, что, поскольку ни одна из последовательностей преобразования ни в коем случае не лучше, чем другая (на самом деле они полностью идентичны), выбирается более специализированный шаблон.
template <class T> void bar(X<T>); // #1
template <class T> void bar(X<T&>); // #2
// Call:
bar<int>( F<int>() );
Возвращаясь к [temp.deduct.partial], выполняется определение типа. Уникальный тип, назовите его Unique
, синтезируется для параметра шаблона каждого шаблона аргумента. Выполняются следующие процедуры с соответствующими результатами — обратите внимание, что при вызове с помощью F<int>
как с F<int&>
(и любая другая специализация F
):
X<Unique&>
выводится против X<T>
, уступая T=Unique&
, С другой стороны,X<Unique>
выводится против X<T&>
, что приводит к неудаче удержания.Как мы видим, шаблон №2 более специализирован. При использовании в качестве шаблона аргумента на шаге 1 вычет был успешным, тогда как для шаблона № 1 в качестве шаблона аргумента на шаге 2 вычет не удался. Поэтому и называется вторая, более специализированная специализация шаблонов функций.
Пример 1:
Компилятор не может знать, хотите ли вы передать a
по ссылке или по значению. Если вы специализируете свой шаблон с T *
он легко узнает, потому что синтаксис вызова функции будет другим foo(&a)
,
Пример 2:
Здесь вы говорите компилятору, что вторая перегрузка qux
занимает X<T &>
поэтому он знает, что вы хотите построить этот объект с T &
, Здесь нет двусмысленности. Но если вы сделаете это:
template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <class T>
void qux(X<T> &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
Вы закончите с той же проблемой
Пример 3:
Та же проблема.
Я не знаю, если это очень ясно, так что если кто-то может улучшить мой ответ, это может быть полезно для автора