Я ожидаю, что последние две строки первого примера кода будут печататься одинаково.
Типы вычитаются так, как я ожидаю, а разрешение перегрузки также соответствует ожиданиям.
Однако, если я явно набираю квалификацию вызова функции, тогда я получаю другой результат, чем когда выводится тип.
Второй пример кода повторяет упражнение, заменяя разрешение перегрузки специализацией. В этом случае все работает так, как можно было ожидать.
Любое объяснение?
РЕДАКТИРОВАТЬ: я добавил еще одну строку, показывающую, что Karthik упоминал о print<R,int>(r);
чего я тоже не понимаю.
Пример кода 1: (перегрузка шаблона функции)
#include <iostream>
template <typename T>
void print (T i)
{
std::cout << "simple" << std::endl;
}
template <template<typename> class FF, typename TT>
void print (FF<TT> i)
{
std::cout << "template" << std::endl;
}
template <typename T1, typename T2>
void print (T1 a)
{
T2 b;
std::cout << "two type parameters" << std::endl;
}
template <>
void print<int>(int i)
{
std::cout << "int" << std::endl;
}
template <typename T>
struct R
{
T x;
};
int main()
{
R<int> r;
print<int>(1.1); // ok, prints "int"print(1.1); // ok, prints "simple"print<int>(1); // ok, prints "int"print(1); // ok, prints "int"print(r); // ok, prints "template"print<int,int>(1); // ok, prints "two type parameters"print<R<int>,int>(r); // ok, prints "two type parameters"print<R<int> >(r); // (1) ?? why "simple" ??
print<R,int >(r); // (2) ?? prints "template", why does it compile at all ??
// gcc 4.6.2 (-std=c++0x) and 4.8.1 (-std=c++11)
// clang++ 3.3.1 same behavior as gcc
}
Пример кода 2: (специализация шаблона класса).
#include <iostream>
template <typename T>
struct P
{
static void print (T i)
{
std::cout << "simple" << std::endl;
}
};
template <template<class TT> class FF, typename TT>
struct P <FF<TT> >
{
static void print (FF<TT> i)
{
std::cout << "template" << std::endl;
}
};
template <>
struct P<int>
{
static void print(int i)
{
std::cout << "int" << std::endl;
}
};
template <typename T>
struct R
{
T x;
};
int main()
{
R<int> r;
P<double>::print(1.1); // ok, prints "simple"P<int>::print(1); // ok, prints "int"P<R<int> >::print(r); // ok, prints "template"//P<R,int >::print(r); // ok, does not compile
}
Что ж, давайте посмотрим, что думает компилятор о каждом из них.
template <typename T> void print (T i); // (1)
template <template<typename> class FF, typename TT> void print (FF<TT> i); // (2)
template <typename T1, typename T2> void print (T1 a); // (3)
template <> void print<int>(int i); // (4)
Хорошо, некоторые предварительные условия: у нас есть три шаблона функций, которые перегружают друг друга (1, 2 и 3), а 4 — это специализация 1.
Все три перегрузки имеют один параметр функции. Кроме того, функции имеют параметры шаблона:
1 имеет параметр шаблона одного типа, который может быть выведен из параметра функции.
2 имеет параметр шаблона шаблона и параметр шаблона типа, оба из которых могут быть выведены из параметра функции.
3 имеет два типа шаблонных параметров, только первый из которых может быть выведен (что делает вывод бесполезным).
Теперь давайте посмотрим на звонки. Когда есть явные аргументы шаблона, компилятор всегда «предварительно фильтрует» перегрузки для тех функций, которые могут быть созданы таким образом.
print<int>(1.1); // ok, prints "int"
Один явный аргумент шаблона типа. 1 совпадений. 2 не соответствует, потому что первый аргумент не шаблон. 3 матча, фиксация T1
в int
; тем не мение, T2
не может быть выведено, поэтому оно тоже отпадет. 1 выбирается параметром T
являющийся int
, Это соответствует специализации 4.
print(1.1); // ok, prints "simple"
Нет явных аргументов шаблона. Начинается дедукция; тип аргумента double
, 1 совпадение; T
двойной. 2 требует шаблона FF<TT>
, а также double
не соответствует этому, поэтому провал. 3 можно вывести T1
в double
, но не имеет ничего для T2
тоже не получается. 1 выбран. Специализация не совпадает.
print<int>(1); // ok, prints "int"
Это идентично первому случаю, за исключением того, что во время окончательного разрешения перегрузки происходит неявное преобразование.
print(1); // ok, prints "int"
Это идентично второму случаю, за исключением того, что выводимый тип int
(все еще не совпадает FF<TT>
), поэтому специализация совпадает.
print(r); // ok, prints "template"
Удержание дает следующие результаты: 1 совпадение, с T = R<int>
, Для 2, R<int>
соответствует шаблону FF<TT>
так жизнеспособно, с FF = R
а также TT = int
, 3, как обычно, не знает, что делать с T2
, Разрешение перегрузки получает идентичные последовательности для 1 и 2 (идентичность), поэтому частичное упорядочение шаблонов функций устраняет неоднозначность: 2 более специализировано, чем 1 и выбрано.
print<int,int>(1); // ok, prints "two type parameters"
Два явных аргумента шаблона типа. 1 принимает только один. 2 хочет шаблон в качестве первого аргумента. 3 осталось.
print<R<int>,int>(r); // ok, prints "two type parameters"
Это идентично предыдущему случаю. Первый аргумент R<int>
вместо int
, но это все еще тип, а 2 не нравится.
print<R<int> >(r); // (1) ?? why "simple" ??
Это идентично первому и третьему случаям. У нас есть один явный аргумент шаблона типа. 3 не может вывести T2
, 2 хочет шаблон в качестве первого аргумента, поэтому 1 — единственный выбор. R<int>
это тип, а не шаблон.
print<R,int >(r); // (2) ?? prints "template",
Здесь у нас есть два явных аргумента шаблона: первый шаблон, второй тип. 1 принимает только один аргумент. 3 хочет тип для своего первого параметра шаблона. 2 с радостью принимает шаблон для первого и тип для второго параметра.
Ключевые уроки здесь:
Редактировать: Ответить на расширенный вопрос.
Когда я заменяю перегрузку функции специализацией шаблона, тогда сопоставление с шаблоном работает так, как я и ожидал. У меня возникли проблемы с верой в то, что правила сопоставления с образцом также различаются между классами и функциями.
Это вопрос перспективы. Для классов и функций не существует правил сопоставления с образцом, поэтому нельзя сказать, отличаются они или нет. Существуют правила сопоставления с образцом для частичной специализации и для вывода аргументов шаблона. Это на самом деле то же самое; раздел о частичной специализации (14.5.5) относится к разделу о выводе аргументов шаблона функции (14.8.2).
Таким образом, правила сопоставления с образцом одинаковы.
Однако вывод аргументов применяется только к функциям (для шаблонов классов вычет аргументов отсутствует, по крайней мере, пока), а частичная специализация применяется только к классам (нельзя частично специализировать функции). В этом ключевое различие между функциями и классами: в первом примере у вас есть два шаблона функций:
template <typename T> void print(T i);
template <template <typename> class FF, typename TT> void print(FF<TT> i);
Это два разных шаблона. Они полностью независимы. Это зависит от сложных правил и взаимодействий явной передачи параметров, вывода аргументов и разрешения перегрузки, чтобы определить, какой из них подразумевается в любом данном вызове. Однако, и это важно, каждый может существовать без другого. Другими словами, представьте, что у вас была только одна функция:
template <template <typename> class FF, typename TT> void something_else(FF<TT> i);
Будете ли вы удивляться, что something_else<R, int>(r);
является действительным? У вас есть шаблон с двумя параметрами, и вы передаете два аргумента. Существование другой шаблон с одним аргументом не меняет этого!
Это важно, поэтому я повторю это: два шаблона функций, даже если они имеют одинаковые имена, полностью независимый шаблоны.
Не так с классами. Если вы попробуете то же самое с классами, компилятор пожалуется:
template <typename T> class Q {};
template <template <typename> class FF, typename TT> class Q {};
Clang говорит:
redef.cc:2:5: error: too many template parameters in template redeclaration
У вас не может быть двух шаблонов классов с одинаковым именем. Компилятор думает, что вы хотите объявить старый Q
снова и жалуется, что списки параметров шаблона не совпадают.
Единственное, что вы можете сделать с шаблонами классов, это специализировать их, как вы это делали во втором примере:
template <typename T> class P {};
template <template <typename> class FF, typename TT> class P<FF<TT>> {};
Но обратите внимание, что это не независимые шаблоны. Они даже не одно и то же. Первый — это шаблон класса, а второй — частичная специализация шаблона класса. Второе полностью зависит от первого; удаление основного шаблона означает, что специализация больше не компилируется:
redef.cc:2:64: error: explicit specialization of non-template class 'P'
В отличие от перегруженных шаблонов функций, частичная специализация шаблона класса не является сущностью, на которую может ссылаться пользователь. Для пользователя есть один шаблон, P
и у него есть один параметр шаблона. Специализация будет соответствовать, если этот один параметр принимает конкретную форму, но в отличие от второго шаблона функции в первом примере, специализация не является независимым шаблоном с двумя параметрами.
Вот почему P<R<int>>::print(r)
компилирует и работает: P
имеет один параметр и R<int>
пропущено для этого. Частичная специализация соответствует шаблону и поэтому выбрана. Но P<R, int>::print(r)
не работает: P
только один параметр шаблона, и здесь вы пытаетесь передать два. Специализация не является собственной сущностью и поэтому не рассматривается.
Но шаблоны функций все независимые, полные шаблоны. Только полная специализация template <> void print<int>(int i)
не является.
Итак, подведем итог:
Это предположение, а не ответ, те, у кого стандарт на кончиках пальцев, могут просветить нас всех,
Но позвольте мне сделать обоснованное предположение
template <template<typename> class FF, typename TT> //......(1)
TT
это тип в то время как FF
считается параметр шаблона
template <typename T> // .....(2)
T
это тип
Теперь, когда явно указывается тип, R<int>
, это конкретный тип, таким образом (2) выбран.
Когда я просил сделать вывод, я предполагаю, что компилятор пробует оба варианта и (1) подходит ближе (или более конкретно), и, таким образом, это выбирается.
При явном указании <R,int>
Конечно, мы используем точную сигнатуру (1), и именно поэтому она выбрана.
+1 к вопросу, я бы этого тоже не ожидал.
Возможно, некоторую полезную информацию можно найти Вот
Линия print< R<int> >(r);
выглядит как один шаблон-параметр print
(он не может угадать, что вы хотите), поэтому он вызывает функцию с одним шаблоном T = R<int>
,
print< R,int >(r);
вызывает функцию с двумя параметрами шаблона, из которых существует только одна версия (шаблон). к счастью R
это шаблон, который может быть создан с помощью int
так что компилируется.
Это напечатало бы просто, потому что тип R<int>
выводится как int
, В вашем случае вам нужно передать 2 параметра в явном виде, чтобы сделать вывод template <template<typename> class FF, typename TT>