Когда два функциональных шаблона считаются частично упорядоченными, а когда - неоднозначными?
Я полностью запутался после прочтения вопроса Как сделать эти параметры std::function однозначными? До сих пор я думал, что понимаю, что такое частичное упорядочение шаблонов функций, но после прочтения этого вопроса я записал три примера, чтобы проверить поведение компилятора, и полученные результаты мне трудно понять.
Пример № 1
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
? Вместо этого компилятор вызывает неоднозначную ошибку вызова.
Пример № 2
#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&)
copy-constructor - лучшая последовательность преобразования, чем X<int>::X(X<int&> const&)
(если так, что делает его лучше, обратите внимание, что аргументы передаются по значению), и поэтому порядок специализаций не имеет значения вообще?
Пример № 3
#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
и без лишних флагов.
2 ответа
Разрешение перегрузки пытается найти лучшую функцию, подобную этой:
(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.
Случай 1:
но не тот, кто принимает
T&
более специализированный, чемT
?
В соответствии с разрешением перегрузки никакое преобразование не является лучшим (оба являются преобразованиями идентификаторов, которые являются точными совпадениями), и поскольку никакой другой пункт в (1) не применяется, выполняется частичное упорядочение. [temp.deduct.partial]/5 говорит, что ссылки заменяются типом, на который они ссылаются в целях частичного упорядочения:
Перед выполнением частичного упорядочения выполняются определенные преобразования для типов, используемых для частичного упорядочения:
- ЕслиP
является ссылочным типом,P
заменяется указанным типом.
- ЕслиA
является ссылочным типом,A
заменяется указанным типом.
Поскольку параметры шаблонов параметров полностью идентичны, нетрудно увидеть, что взаимные выводы успешны в обоих направлениях - поэтому ни один шаблон не является более специализированным, чем другой.
Случай 2:
Частичный заказ здесь не нужен. Определяемое пользователем преобразование из X<int>
в X<int&>
имеет худший ранг, чем преобразование X<int>
в X<int>
- Последнему присваивается рейтинг "Точное совпадение" [over.ics.user]/4:
Преобразованию выражения типа класса в тот же тип класса присваивается рейтинг точного соответствия, […]
Таким образом, это, очевидно, лучшее преобразование, чем X<int>
в X<int&>
, который имеет конверсионный ранг. То же самое происходит и для X<int&>
в X<int>
,
Случай 3:
Третий случай похож на первый. 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
):
- шаблон № 1 в качестве шаблона параметра и шаблон № 2 в качестве шаблона аргумента,
X<Unique&>
выводится противX<T>
, уступаяT=Unique&
, С другой стороны, - шаблон № 2 в качестве шаблона параметра против шаблона № 1 в качестве шаблона аргумента,
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:
Та же проблема.
Я не знаю, если это очень ясно, так что если кто-то может улучшить мой ответ, это может быть полезно для автора