【问题标题】:C++ function template specialization declarations and template arguments; none vs. <> vs. <type>C++ 函数模板特化声明和模板参数; none vs. <> vs. <type>
【发布时间】:2014-11-01 12:28:25
【问题描述】:

在研究函数模板时,我看到以不同方式声明的特化:

template<> void f(argtype) {}
template<> void f<>(argtype) {}
template<> void f<argtype>(argtype) {}

...我想知道它们之间的区别。鉴于以下带有和不带参数的模板函数示例,我有几个问题。

#include <iostream>
#include <typeinfo>

//Function print1 WITH function parameter---------------------------------------------
template<class T>
void print1(T) { std::cout << "Primary template for print1() with type " << typeid(T).name() <<  std::endl; }

template<>
void print1<int>(int) { std::cout << "Specialization for print1<int>(int)" << std::endl; }

//Not allowed, deduced to be the same as print1<int>(int)
/*template<>
void print1<>(int) { std::cout << "Specialization for print1<>(int)" << std::endl; }*/

//Not allowed, deduced to be the same as print1<int>(int)
/*template<>
void print1(int) { std::cout << "Specialization for print1(int)" << std::endl; }*/

//Function print2 WITHOUT function parameter------------------------------------------
/*Not allowed together with print<>(); compiler complains: 
    t2.cpp:29:6: error: template-id 'print2<>' for 'void print2()' does not match any template declaration*/
/*template<class T>
void print2() { std::cout << "Primary template for print2()" << std::endl; }*/

template<class T = short> //Declaration of print2<>() now ok in conjunction with print2<>()
void print2() { std::cout << "Primary template for print2()" << std::endl; }

template<>
void print2<int>() { std::cout << "Specialization for print2<int>()" << std::endl; }

template<>
void print2<>() { std::cout << "Specialization for print2<>()" << std::endl; }

int main() {
    //These three work in the same way, no matter which call method we use, so far so good
    print1(10);
    print1<>(10);
    print1<int>(10);
    print1(true);
    print1<>(true);
    print1<bool>(true);

    print2(); //Triggers print2<>(), a bit unexpectedly, should trigger print2<short>() (primary template)
    print2<>(); //Triggers print2<>(), a bit unexpectedly, should trigger print2<short>() (primary template)
    print2<bool>(); //Triggers print2<bool>() primary template
    print2<short>(); //Triggers print2<>(), should definately trigger primary template for print2()
    print2<int>(); //Triggers print2<int>() specialization
    return 0;
}

输出:

Specialization for print1<int>(int)
Specialization for print1<int>(int)
Specialization for print1<int>(int)
Primary template for print1() with type b
Primary template for print1() with type b
Primary template for print1() with type b
Specialization for print2<>()
Specialization for print2<>()
Primary template for print2()
Specialization for print2<>()
Specialization for print2<int>()
  • 将模板特化参数留空、不存在或使用特化类型会产生什么特殊含义,它对结果有何影响? 似乎对于函数参数,这个规范是多余的,无论它是如何指定的,编译器都会推导出它(结果等效的显式规范变成了不允许的重新声明)。
  • 我知道给定一个没有参数的函数,在声明中明确需要专门的模板参数来指定定义函数的实例化 适用于(因为不能以其他方式推断)。但是在这种情况下,含义似乎暗示了更多的东西,并且“空”特化()以某种无法预料的方式触发。怎么会?
  • 为什么在使用 print2() 专门化 print2 时我必须有一个默认模板参数,但没有它就不行?

【问题讨论】:

    标签: c++ function templates template-specialization


    【解决方案1】:

    离开模板有什么特殊意义 特化参数为空、不存在或具有特化 类型以及它如何影响结果?

    如果您确实提供了一个完整的模板参数列表,那么您只是为一组给定的模板参数显式特化了函数模板。

    如果您为模板形参的一个(可能为空的)子集提供实参,那么您就是在为一组必须推导的实参显式特化函数模板。根据[temp.deduct.decl]:

    在一个声明中,其 declarator-id 指的是 函数模板,模板实参推导 确定声明所指的专业化。 具体来说,这是为显式实例化(14.7.2)完成的, 显式特化 (14.7.3) 和某些友元声明 (14.5.4)。 […]。在所有这些情况下,P 是函数的类型 模板被视为潜在匹配,A 是 [...] 声明中的函数类型 […]。
    扣除是这样的 在 14.8.2.5 中描述。

    如果,对于这样考虑的函数模板集,有 部分排序后没有匹配或多于一个匹配 考虑(14.5.6.2),扣除失败,在申报情况下, 程序格式不正确。

    因此,对于没有给出参数的每个参数,或者在根本没有指定列表的情况下,模板参数推导是针对专业化的每个相应参数及其来自主模板的对应参数进行的。该过程在 §14.8.2.5 中进行了描述,其工作方式就像我们使用提供的模板实参列表作为模板实参和特化中的参数类型的对象作为函数实参调用主模板一样。

    您应该熟悉这样一个事实,即可以使用指定的模板参数调用函数模板,例如

    template <typename A, typename B> void foo(A, B);
    
    foo(7684, 48.);
    foo<int>(7684, 48.);
    foo<int, double>(7684, 48.);
    

    这同样适用于显式特化:

    template <typename T, typename U>
    void foo(T, U) {}
    
    // Both template arguments have to be deduced.
    template<> void foo(double, float);
    
    // The *exact* same as above.
    // template<> void foo<>(double, float);
    
    // Second template argument has to be deduced by type.
    // If we call foo<int>(int(), float()) then the deduced specialization is
    // foo<int, float>, thus U=float.
    template<> void foo<int>(int, float);
    
    template<> void foo<int, int>(int, int);
    

    这也可以应用于函数模板的重载。为了找到一个特化对应的主要模板,选择了最特化的模板。

    template <typename T, typename U>
    void foo(T&, U&) {}
    
    template <typename T, typename U>
    void foo(T const&, U&) {}
    
    // Specializes the second overload because it is more specialized.
    template <>
    void foo(int const&, float&);
    

    请注意,在查找主模板时,提供的参数(即不被推导)用于检查主模板的结果函数参数与特化的结果函数参数。他们必须是平等的。

    template <typename T, typename U>
    void foo(T&, U&) {}
    
    // Error - no matching primary template found.
    template <>
    void foo<int, int>(float&, int&);
    
    // Dito:
    template <>
    void foo<int>(int, int&);
    

    似乎有了函数参数,这个规范是 多余的,无论如何指定,编译器都会推导出来 (结果等效的显式规范变为 不允许的重新声明)。

    是的,确实如此。考虑一下,如果您指定的模板参数无效,则会导致错误:

    但在这种情况下,这个意思似乎暗示了更多的东西,而且 “空”特化 () 以某种无法预料的方式触发 方法。怎么会?

    对于调用,首先推导出模板参数。然后调用具有这些模板参数的特化。

    如果你为这个特定的特化显式特化了一个函数模板,这里是print2&lt;&gt;,也就是print2&lt;short&gt;,那么这个显式特化就会被调用。
    这在什么方面是不可预见的?

    为什么我在专业化的时候必须有一个默认的模板参数 print2print2&lt;&gt;() 但不是没有?

    因为编译器无法推断出参数。如果你提供一个默认参数,他一开始就没有推断它。

    【讨论】:

    • 谢谢!什么是偏序?此外,模板参数列表只是我选择显式提供哪些参数以及我选择推导哪些参数的指令,无论是在实例化还是声明方面?因此调用 foo(int, int) 将实例化为显式特化 foo 并且声明 foo(double, int) 确实,无论我们如何看待它,都是 foo(双,整数)?此外,模板何时被视为重载,何时被视为同一模板的一部分(特化)?
    • @fast-reflexes 是的,参数列表指定应该推导哪些参数,哪些不推导。 foo(double, int)可以,如果推论是这样的话,和foo&lt;double, int&gt;(double, int)一样。如果另一个主函数模板在同一范围内具有相同的名称(其中主函数模板是不是显式特化的函数模板),则主函数模板将重载其名称。
    • Gotcha... 并且显式的特化相当于在函数名之后带有 的函数模板声明和带有函数名且参数不是模板参数的相同声明? (例如 foo(int) 和 foo(int),但不是 foo(T))
    • @fast-reflexes 粗略 - 如果 template&lt;&gt; 是前面的,当然。
    【解决方案2】:

    将模板特化参数留空会有什么特殊含义

    如果可能,推导出缺失的参数;空参数列表意味着要推导所有参数。

    不存在

    这意味着您要声明主模板,而不是明确的特化。

    或使用特殊类型

    这意味着您正在为该类型声明一个明确的特化。

    “空”特化()以某种无法预料的方式触发。怎么会?

    在这两种情况下,都会推导出模板参数。在print1 中,主模板声明T 与函数参数的类型相同;所以它是从函数参数推导出来的。在print2 中,它使用short 的默认类型声明,因此使用它。因此,当您认为应该是 print2&lt;short&gt; 时看到 print2&lt;&gt; 时您的惊讶解释如下:print2&lt;&gt; print2&lt;short&gt;

    为什么在使用 print2() 专门化 print2 时我必须有一个默认模板参数,但没有它?

    如果既没有默认参数也没有函数参数可以从中推断出参数,那么就不可能推断出专门化的类型,因此不能使用&lt;&gt;。我不知道您所说的“没有它”是什么意思;如果您的意思是“没有&lt;&gt;”,那么您声明的是主模板,而不是专业化,并且参数是通用的。

    【讨论】:

    • 谢谢!那么您在 print2 中的观点是,只要要推导所有参数,就使用专门化 print2 吗?如果是这样,那是有道理的,但在 的情况下,我实际上明确地提供了它,所以编译器不必推断它,所以使用 print2 特化没有意义吗?我猜编译器可能会将短参数视为空参数,因为它是默认值,因此最终还是使用了 特化,但我确实明确给出了它,因此不需要进行推断。
    • 另外,你的意思是不是例如在 print1(int) 的声明中,我实际上是在声明一个与 print1(T) 不属于同一模板的新模板(因为 print1 (int) 有一个不存在的模板参数列表...)?
    • @fast-reflexes:“在 &lt;short&gt; [...] 的情况下,使用 print2&lt;&gt; 专业化没有意义” - 再次阅读我的答案。 print2&lt;&gt; print2&lt;short&gt;。当您声明 &lt;&gt; 特化时,缺少的参数被推断为 short,因为这是默认值;所以你声明了&lt;short&gt;的专业化。
    • @fast-reflexes:“你的意思是,例如在 print1(int) 的声明中,我实际上是在声明一个新模板” - 不,你是在重新声明 @987654338 的专业化@,但将模板参数从函数参数类型推导出来,而不是显式提供。
    猜你喜欢
    • 2021-05-23
    • 1970-01-01
    • 1970-01-01
    • 2015-05-08
    • 1970-01-01
    • 1970-01-01
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多