我希望第一个代码示例的最后两行打印相同。
按照我的预期扣除类型,重载分辨率也如我所料。 但是,如果我显式地键入了限定函数调用,那么我会得到一个不同的结果,然后推断出类型。
第二个代码示例重复使用专门化替换重载决策的练习。在这种情况下,一切都按照任何人的预期运作。
有任何解释吗?
编辑:我再添加一行,显示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
}
答案 0 :(得分:3)
好吧,让我们看看编译器对这些内容的看法。
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有两个类型的模板参数,只能推断出第一个模板参数(使得演绎没用)。
现在让我们来看看这些电话。当有明确的模板参数时,编译器将始终&#34;预过滤&#34;那些可以通过这种方式实例化的函数的重载。
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)
不是。
总结一下:
答案 1 :(得分:2)
这是一个猜测不是答案,那些在他们的指尖有标准的人可以启发我们所有人,
但是让我采取有根据的猜测
template <template<typename> class FF, typename TT> //......(1)
TT
是一种类型,而FF
被视为template template parameter
template <typename T> // .....(2)
T
是一种类型
现在当明确指定类型R<int>
时,它是一个具体类型,因此(2)被选中。
当要求它推断时,我猜编译器会尝试两个选项并且(1)更接近(或更具体),因此选择它。
当明确指定<R,int>
时,我们使用(1)的确切签名,这就是为什么选择它。
+1问题,我也不会预料到这一点。
也许可以找到一些有用的信息here
答案 2 :(得分:2)
行print< R<int> >(r);
看起来像单个模板参数print
(它无法猜出你想要的是什么)所以它用T = R<int>
调用单模板参数函数。
print< R,int >(r);
调用双模板参数函数,其中只有一个版本(模板)。幸运的是,R
是一个可以用int
实例化的模板,因此它可以编译。
答案 3 :(得分:0)
它会打印很简单,因为类型R<int>
被推断为int
。在您的情况下,您需要将2个参数传递给explicitley,使其推导为template <template<typename> class FF, typename TT>