简体   繁体   English

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

[英]Differences between template specialization and overloading for functions?

So, I know that there is a difference between these two tidbits of code: 所以,我知道这两个代码之间存在差异:

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

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

and

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

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

I am confused as to what the functional differences between these two are. 我对这两者之间的功能差异感到困惑。 Can someone show some situations where these snippits act differently from each other? 有人可以显示某些情况,这些snippits彼此之间的行为不同吗?

I can only think of a few differences - here are some examples that don't necessarily cause harm (i think). 我只能想到一些差异 - 这里有一些不一定会造成伤害的例子(我认为)。 I'm omitting definitions to keep it terse 我省略了定义以保持简洁

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

That is because specializations are not found by name lookup, but by argument matching, so a using declaration will automatically consider a later introduced specialization. 这是因为通过名称查找找不到特化,但是通过参数匹配,因此using声明将自动考虑稍后引入的特化。

Then, you of course cannot partially specialize function templates. 那么,你当然不能部分专门化功能模板。 Overloading however accomplishes something very similar by partial ordering (using different types now, to make my point) 然而,重载通过部分排序完成了非常类似的事情(现在使用不同的类型,以表达我的观点)

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
}

That wouldn't be possible with function template explicit specialization. 使用函数模板显式特化是不可能的。 Another example is when references are involved, which causes template argument deduction to look for an exact match of the types involved (modulo base/derived class relationships and constness): 另一个例子是涉及引用时,这会导致模板参数推导寻找所涉及类型的完全匹配(模基/派生类关系和常量):

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);
}

I recommend you to always use overloading, because it's richer (allows something like partial specialization would allow), and in addition you can place function in whatever namespace you want (although then it's not strictly overloading anymore). 我建议你总是使用重载,因为它更丰富(允许部分特化允许的东西),此外你可以将函数放在你想要的任何命名空间中(虽然它不再严格超载)。 For example, instead of having to specialize std::swap in the std:: namespace, you can place your swap overload in your own namespace and make it callable by ADL. 例如,您可以将swap重载放在您自己的命名空间中,并使其可由ADL调用,而不必在std:: namespace中专门化std::swap

Whatever you do, never mix specialization and overloading , it will be a hell of a mess like this article points out. 无论你做什么, 永远不要混合专业化和超载 ,这将是一个混乱,就像这篇文章指出的那样。 The Standard has a lovely paragraph about it 该标准有一个可爱的段落

The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member class templates of class templates, member function templates of class templates, member functions of member templates of class templates, member functions of member templates of non-template classes, member function templates of member classes of class templates, etc., and the placement of partial specialization declarations of class templates, member class templates of non-template classes, member class templates of class templates, etc., can affect whether a program is well-formed according to the relative positioning of the explicit specialization declarations and their points of instantiation in the translation unit as specified above and below. 为函数模板,类模板,类模板的成员函数,类模板的静态数据成员,类模板的成员类,类模板的成员类模板,类模板的成员函数模板,成员的成员函数放置显式特化声明类模板的模板,非模板类的成员模板的成员函数,类模板的成员类的成员函数模板等,类模板的部分特化声明的放置,非模板类的成员类模板,成员类模板等的类模板可以根据显式特化声明的相对位置及其在翻译单元中的实例化点的相对位置来影响程序是否格式良好,如上下文所述。 When writing a specialization, be careful about its location; 写专业时,要注意它的位置; or to make it compile will be such a trial as to kindle its self-immolation. 或者使它编纂将是一种试图点燃其自焚的试验。

Template specialization is more generic than just overloading. 模板专业化比仅仅重载更通用。 You can specialize things like classes rather than just simple functions. 你可以专注于类,而不仅仅是简单的函数。 Overloading only applies to functions. 重载仅适用于函数。

UPDATE: To clarify more per AraK's comment, you are really comparing apples and oranges here. 更新:为了澄清更多AraK的评论,你真的在​​这里比较苹果和橘子。 Function overloading is used to introduce the ability to have different functions share a single name, if they have different signatures. 函数重载用于引入具有不同函数的能力,如果它们具有不同的签名,则共享单个名称。 Template specialization is used to define a specific code snippet for a specific type parameter. 模板特化用于定义特定类型参数的特定代码段。 You can't have a template specialization if you don't have a template. 如果您没有模板,则无法进行模板专业化。 If you remove the first piece of code that declares the generic template, you'll receive a compile time error if you try to use template specialization. 如果删除声明通用模板的第一段代码,如果尝试使用模板特化,则会收到编译时错误。

So, the goal of template specialization is pretty different from a function overload. 因此,模板特化的目标与函数重载完全不同。 They just happen to behave similarly in your example while they are fundamentally different. 它们恰好在你的例子中表现得相似,但它们根本不同。

If you provide an overload, you are declaring an independent method that happens to have the same name. 如果提供重载,则声明一个恰好具有相同名称的独立方法。 You are not preventing the template to be used with the specific type parameter. 您没有阻止模板与特定类型参数一起使用。 To demonstrate this fact, try: 为了证明这一事实,请尝试:

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.
}

As you can see, in this example, there are two distinct inc functions for int values: inc(const int&) and inc<int>(const int&) . 如您所见,在此示例中, int值有两个不同的inc函数: inc(const int&)inc<int>(const int&) You couldn't expand the generic template using int if you had used template specialization. 如果您使用了模板特化,则无法使用int扩展通用模板。

One such example: 一个这样的例子:

#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);
}

It is actually suggested that you use non-template overloads for functions and leave specialization for classes. 实际上,建议您对函数使用非模板重载,并为类保留特殊化。 It is discussed at greater length in Why Not Specialize Function Templates? 为什么不专业化功能模板对它进行了更详细的讨论

AFAIK there is no functional difference. AFAIK没有功能差异。 All I can add is that if you have both a template function specialisation and an ordinary function defined then there is no overload ambiguity as the ordinary function is favoured. 我可以添加的是,如果您同时具有模板函数特化和定义的普通函数,则不存在过载歧义,因为普通函数是有利的。

Just to elaborate on the first point mentioned by litb in his answer . 只是详细说明litb在他的回答中提到的第一点。 Specializations are only checked once overload resolution has actually selected a primary template. 只有在重载决策实际选择了主模板后才会检查专精。 The result can lead to some surprises where a function is overloaded and has explicit specializations: 结果可能导致一些意外,其中函数被重载并具有明确的特化:

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);
}

When choosing which function to call, the following steps take place: 选择要调用的函数时,将执行以下步骤:

  1. Name lookup finds both primary templates. 名称查找查找两个主模板。
  2. Each template is specialized and overload resolution attempts to select a best function based on conversions between the arguments and parameters. 每个模板都是专用的,重载决策尝试根据参数和参数之间的转换选择最佳函数。
  3. In thise case, there is no difference in the quality of the conversions. 在这种情况下,转换质量没有差异。
  4. Partial ordering rules are then used to select the most specialized template. 然后使用部分排序规则来选择最专业的模板。 In this case that is the second parimary "foo(T*)". 在这种情况下,这是第二个parimary“foo(T *)”。

Only after these steps, when the best function has been selected will explicit specializations of the selected function be considered. 只有在这些步骤之后,当选择了最佳函数时,才会考虑所选函数的显式特化。 (In this case primary #2 has none so none are considered). (在这种情况下,主#2没有,所以没有考虑)。

The only way to call the above explicit specialization here, is to actually use explicit template arguments in the call: 在这里调用上述显式特化的唯一方法是在调用中实际使用显式模板参数:

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

A good rule of thumb is to try to avoid having overloads that are also explicily specialized, as the resulting rules are pretty complex. 一个好的经验法则是尽量避免具有明显专门化的重载,因为生成的规则非常复杂。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM