模板专业化和功能重载之间的差异?

时间:2009-10-02 21:39:14

标签: c++ templates overloading

所以,我知道这两个代码之间存在差异:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

template <>
int inc(const int& t)
{
    return t + 1;
}

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 1;
}

我对这两者之间的功能差异感到困惑。有人可以说某些情况下这些snippits的行为不同吗?

5 个答案:

答案 0 :(得分:14)

我只能想到一些差异 - 这里有一些不一定会造成伤害的例子(我认为)。我省略了定义以保持简洁

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
template <> int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses explicit specialization

// --- against ---

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses template

这是因为通过名称查找找不到特化,但是通过参数匹配,因此using声明将自动考虑稍后引入的特化。

然后,你当然不能部分专门化功能模板。然而,重载通过部分排序完成了非常类似的事情(现在使用不同的类型,以表明我的观点)

template <typename T> void f(T t); // called for non-pointers
template <typename T> void f(T *t); // called for pointers.

int a;
void e() {
  f(a); // calls the non-pointer version
  f(&a); // calls the pointer version
}

使用函数模板显式特化是不可能的。另一个例子是涉及引用时,这会导致模板参数推导寻找所涉及类型的完全匹配(模数基/派生类关系和常量):

template<typename T> void f(T const &);
template<> void f(int * const &);

template<typename T> void g(T const &);
void g(int * const &);

int a[5];
void e() {
  // calls the primary template, not the explicit specialization
  // because `T` is `int[5]`, not `int *`
  f(a);

  // calls the function, not the template, because the function is an
  // exact match too (pointer conversion isn't costly enough), and it's 
  // preferred. 
  g(a);
}

我建议你总是使用重载,因为它更丰富(允许部分特化允许的东西),此外你可以将函数放在你想要的任何命名空间中(虽然它不再严格超载)。例如,您不必在std::swap命名空间中专门化std::,而是可以将swap重载放在您自己的命名空间中,并使其可由ADL调用。

无论你做什么,从不混合专业化和重载,它会像this article指出的那样混乱。该标准有一个可爱的段落

  

为函数模板,类模板,类模板的成员函数,类模板的静态数据成员,类模板的成员类,类模板的成员类模板,类模板的成员函数模板,成员放置显式特化声明类模板的成员模板的功能,非模板类的成员模板的成员函数,类模板的成员类的成员函数模板等,以及类模板的部分特化声明的放置,非模板的成员类模板类,类模板的成员类模板等可以根据显式专业化声明的相对位置及其在翻译单元中的实例化点(如上下文所述)来影响程序是否格式良好。写专业时,要注意它的位置;或者使它编纂将是一种试图点燃其自焚的试验。

答案 1 :(得分:5)

模板专业化比仅重载更通用。你可以专注于类,而不仅仅是简单的函数。重载仅适用于函数。

更新:根据AraK的评论澄清更多信息,你真的在​​这里比较苹果和橘子。函数重载用于引入具有不同函数的能力,如果它们具有不同的签名,则共享单个名称。模板特化用于定义特定类型参数的特定代码段。如果您没有模板,则无法进行模板专业化。如果删除声明通用模板的第一段代码,如果尝试使用模板特化,则会收到编译时错误。

因此,模板特化的目标与函数重载完全不同。它们恰好在你的例子中表现得相似,但它们根本不同。

如果提供重载,则声明一个恰好具有相同名称的独立方法。您没有阻止模板与特定类型参数一起使用。为了证明这一事实,请尝试:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 42;
}

#include <iostream>
int main() {
   int x = 0;
   x = inc<int>(x);
   std::cout << "Template: " << x << std::endl; // prints 1.

   x = 0;
   x = inc(x);
   std::cout << "Overload: " << x << std::endl; // prints 42.
}

如您所见,在此示例中,inc值有两个不同的int函数:inc(const int&)inc<int>(const int&)。如果您使用了模板专业化,则无法使用int扩展通用模板。

答案 2 :(得分:4)

一个这样的例子:

#include <cstdio>

template <class T>
void foo(T )
{
    puts("T");
}

//template <>
void foo(int*)
{
    puts("int*");
}

template <class T>
void foo(T*)
{
    puts("T*");
}

int main()
{
    int* a;
    foo(a);
}

实际上,建议您对函数使用非模板重载,并为类保留特殊化。在Why Not Specialize Function Templates?

中对它进行了更详细的讨论

答案 3 :(得分:1)

AFAIK没有功能差异。我可以补充的是,如果您同时具有模板函数特化和定义的普通函数,则不存在重载歧义,因为普通函数是有利的。

答案 4 :(得分:1)

在他的answer中详细说明litb提到的第一点。只有在重载决策实际选择了主模板后才会检查专精。结果可能导致一些意外,其中函数被重载并具有明确的特化:

template <typename T> void foo (T);  // Primary #1
template <> void foo<int*> (int*);   // Specialization of #1

template <typename T> void foo (T*); // Primary #2

void bar (int * i)
{
  foo(i);
}

选择要调用的函数时,将执行以下步骤:

  1. 名称查找会找到两个主要模板。
  2. 每个模板都是专用的,重载决策会尝试根据参数和参数之间的转换选择最佳函数。
  3. 在这种情况下,转换质量没有差异。
  4. 然后使用部分排序规则来选择最专业的模板。在这种情况下,这是第二个parimary“foo(T *)”。
  5. 仅在这些步骤之后,当选择了最佳功能时,才会考虑所选功能的显式特化。 (在这种情况下,主要#2没有,所以没有考虑)。

    在这里调用上述显式特化的唯一方法是在调用中实际使用显式模板参数:

    void bar (int * i)
    {
      foo<int*> (i);  // expliit template argument forces use of primary #1
    }
    

    一个好的经验法则是尽量避免具有明显特殊的重载,因为生成的规则非常复杂。

相关问题