2

我希望第一个代码示例的最后两行打印相同。

类型按我预期扣除,重载分辨率也如我预期。但是,如果我显式地键入限定函数调用,那么当推导类型时,我会得到不同的结果。

第二个代码示例重复了用专业化替换重载决议的练习。在这种情况下,一切都会按任何人的预期进行。

有什么解释吗?

编辑:我又添加了一行,显示了 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
}
4

4 回答 4

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 有两个类型模板参数,只能推导第一个(推导没用)。

现在让我们看看调用。当有显式模板参数时,编译器将始终“预过滤”那些可以以这种方式实例化的函数的重载。

    print<int>(1.1);          // ok, prints "int"

一个显式类型模板参数。1 匹配。2 不匹配,因为第一个参数不是模板。3 匹配,固定T1int; 但是,T2不能推导出来,所以它也消失了。选择 1,参数Tint。这与专业化 4 相匹配。

    print(1.1);               // ok, prints "simple"

没有明确的模板参数。扣款开始;参数类型是double. 1场比赛;T是双倍的。2 需要 pattern FF<TT>,并且double不匹配,所以失败。3 可以推断T1double,但没有T2,所以它也失败了。1 被选中。专业不匹配。

    print<int>(1);            // ok, prints "int"

这与第一种情况相同,只是在最终重载决议期间,会发生隐式转换。

    print(1);                 // ok, prints "int"

这与第二种情况相同,只是推导的类型是int(still doesn't match FF<TT>),因此特化匹配。

    print(r);                 // ok, prints "template"

演绎得出以下结果: 1 个匹配项,其中T = R<int>. 对于 2,R<int>匹配模式FF<TT>,因此它是可行的,与FF = RTT = 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 很高兴将模板作为其第一个参数,将类型作为其第二个参数。

这里的主要教训是:

  • 在任何推导或重载决议发生之前,显式模板参数与模板参数匹配,因此首先只有一些函数匹配。
  • 1 和 2 是重载,重载决议分别为它们发生。如果 2 是 1 的特化,情况会有所不同,但不存在偏函数模板特化。
  • 在你给它参数之前,它只是一个模板。实例化的函数模板是一个函数。实例化的类模板是一个类(因此也是一种类型)。类模板的实例化和非模板类的实例化没有区别,除了参数推导和部分特化所做的模式匹配。

编辑:回答扩展问题。

当我用模板特化替换函数重载时,模板模式匹配就像我预期的那样工作。我很难相信模式匹配规则在类和函数之间也不同。

这是一个观点问题。类和函数没有模式匹配规则,所以你不能说它们有什么不同。部分特化和模板参数推导有模式匹配规则。这些实际上是相同的;部分特化部分(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 {};

铿锵声 说:

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)不是。

所以总结一下:

  • 函数模板可以完全专门化或重载。重载的函数模板是完全独立的:当您想知道可以或应该显式提供哪些参数时,依次查看它们的所有参数列表。
  • 避免专门化功能模板。它以奇怪的方式与重载的函数模板交互,你最终可能会得到一个与你期望调用的函数不同的函数。只需重载函数模板,以及其他函数模板和普通函数。偏序规则很直观;请记住,当有疑问时,会选择普通函数而不是模板。
  • 类模板可以完全或部分特化,但不能重载。专业化不是独立的模板;实例化类模板时,您始终必须转到参数列表的主模板。
  • 选择偏特化和从函数参数推导模板参数的匹配规则是相同的;但是,当您将模板参数显式传递给函数模板时,不会对这些参数执行推导。
于 2013-09-02T16:58:40.810 回答
2

这是一个猜测不是答案,那些有标准的人可以启发我们所有人,

但让我做一个有根据的猜测

template <template<typename> class FF, typename TT>   //......(1)

TT是一种类型,而FF被视为模板模板参数

template <typename T>    // .....(2)

T是一种类型

现在,当显式指定类型时R<int>,它是一个具体类型,因此选择了 (2)。

当要求它进行推断时,我猜编译器会尝试这两个选项,并且 (1) 更适合(或更具体),因此选择了它。

当明确指定 时<R,int>,我们当然会使用 (1) 的确切签名,这就是选择该签名的原因。

+1 的问题,我也没想到会这样。

也许可以在这里找到一些有用的信息

于 2013-09-02T04:31:33.617 回答
2

该行print< R<int> >(r);看起来像一个单模板参数print(它无法猜测您想要什么),因此它使用T = R<int>.

print< R,int >(r);需要两个模板参数的函数,其中只有一个版本(模板)。幸运R的是,它是一个可以实例化的模板,int因此它可以编译。

于 2013-09-02T05:51:20.070 回答
0

它会打印简单,因为类型R<int>被推断为int. 在您的情况下,您需要将 2 个参数传递给 explicitley 使其推断为template <template<typename> class FF, typename TT>

于 2013-09-02T04:17:24.093 回答