函数重载匹配模板模板

时间:2013-09-02 04:06:26

标签: c++ overload-resolution template-templates

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

按照我的预期扣除类型,重载分辨率也如我所料。 但是,如果我显式地键入了限定函数调用,那么我会得到一个不同的结果,然后推断出类型。

第二个代码示例重复使用专门化替换重载决策的练习。在这种情况下,一切都按照任何人的预期运作。

有任何解释吗?

编辑:我再添加一行,显示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 个答案:

答案 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时参数Tint。这与专业化4匹配。

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

没有明确的模板参数。扣除开始;参数类型为double。 1场比赛; T是双倍的。 2需要模式FF<TT>,而double不匹配,因此失败。 3可以推导T1double,但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 = 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 {};

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>