【问题标题】:function overload matching template template函数重载匹配模板模板
【发布时间】:2013-09-02 04:06:25
【问题描述】:

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

类型按我预期扣除,重载决议也如我预期。 但是,如果我明确地键入限定函数调用,那么我得到的结果与推断类型时不同。

第二个代码示例重复了用特化替换重载解析的练习。在这种情况下,一切都会按预期进行。

有什么解释吗?

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

【问题讨论】:

  • 你可以试试print&lt;R, int&gt;
  • 是的,这行得通,我认为这是一个很好的提示。但此刻我还是不明白。
  • 我认为你的最后一句话传递了一种类型,而你的“模板”模板需要&lt;R,int&gt; 表示法。可能这就是类型推断的推断和传递方式
  • 等等,你说print&lt;R, int&gt;(r); 工作和打印template?这让我很困惑。为什么会这样做?
  • @anthropomorphic output

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


【解决方案1】:

好吧,让我们看看编译器是怎么看待这些的。

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,参数 Tint。这符合专业化 4。

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

没有明确的模板参数。扣款开始;参数类型是double。 1场比赛; T 是双倍的。 2 需要模式FF&lt;TT&gt;,而double 不匹配,所以失败。 3 可以将T1 推导出为double,但是T2 没有任何内容,所以它也失败了。 1 被选中。专精不匹配。

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

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

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

这与第二种情况相同,只是推导的类型是int(仍然不匹配FF&lt;TT&gt;),所以特化匹配。

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

演绎得出以下结果:1 个匹配项,与 T = R&lt;int&gt;。对于 2,R&lt;int&gt; 匹配模式 FF&lt;TT&gt;,因此它是可行的,具有 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&lt;int&gt;而不是int,但这仍然只是一个类型,2不喜欢它。

    print<R<int> >(r);        // (1) ?? why "simple" ??

这与第一种和第三种情况相同。我们有一个显式类型模板参数。 3 无法推导出T2,2 想要一个模板作为第一个参数,所以 1 是唯一的选择。 R&lt;int&gt; 是一种类型,而不是模板。

    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&lt;R, int&gt;(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&lt;R&lt;int&gt;&gt;::print(r) 编译和工作的原因:P 有一个参数,R&lt;int&gt; 被传递给它。部分特化与模式匹配,因此被选中。但是P&lt;R, int&gt;::print(r) 不起作用:P 只有一个模板参数,而在这里您尝试传递两个。专业化不是它自己的实体,因此不予考虑。

但功能模板都是独立的,完整的模板。只有完全专业化template &lt;&gt; void print&lt;int&gt;(int i) 不是。

总结一下:

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

【讨论】:

  • 对应于示例 (1) print&lt;R&lt;int&gt; &gt;(r) 你说“2 想要一个模板作为第一个参数”。这是你推理中的关键陈述。为什么R&lt;int&gt; 不是模板?。
  • R 是一个类模板。 R&lt;int&gt; 是该模板的实例化,因此它是一种类型。只要你不给它参数,它只是一个模板。
  • +1 用于解释为什么 R&lt;int&gt; 不匹配。但我仍然不相信R,int 应该匹配。我将不回答这个问题以获得更多建议。
  • 这个答案是绝对正确的。查看第二个特化:模板
  • 我在问题中添加了我的示例的变体。在这个例子中,&lt;R, int&gt; 被编译器拒绝,R&lt;int&gt; 完全符合我的预期
【解决方案2】:

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

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

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

TT 是一个类型,而FF 被认为是一个template template parameter

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

T 是一个类型

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

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

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

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

或许可以找到一些有用的信息here

【讨论】:

    【解决方案3】:

    print&lt; R&lt;int&gt; &gt;(r); 行看起来像一个单一模板参数 print(它无法猜测你想要什么),因此它使用 T = R&lt;int&gt; 调用单一模板参数函数。

    print&lt; R,int &gt;(r); 调用一个有两个模板参数的函数,其中只有一个版本(模板)。幸运的是R 是一个模板,可以用int 实例化,所以它可以编译。

    【讨论】:

    • 为什么R可以用int实例化是幸运的? OP 的代码没有实例化任何R&lt;int&gt;s,是吗?
    【解决方案4】:

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

    【讨论】:

    • 我不是在寻找解决方案。我很好,不必输入限定符。我很懒。我正在寻找解释。我不同意你的参数计数,至少现在还没有。
    • “推断为”是什么意思?我希望您不要说R&lt;int&gt;int 相同,因为您肯定错了。
    • @anthropomorphic 通过推断我的意思是它选择的最接近的匹配
    • 我对类型推导的工作方式非常满意。对我来说奇怪的是重载决议。
    猜你喜欢
    • 2017-05-04
    • 1970-01-01
    • 2013-08-27
    • 1970-01-01
    • 2011-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多