功能模板专业化的重要性和必要性

Posted

技术标签:

【中文标题】功能模板专业化的重要性和必要性【英文标题】:Function template specialization importance and necessity 【发布时间】:2010-02-04 03:29:54 【问题描述】:

我读过 C++ Primer,它说函数模板专业化是一个高级主题,但我完全迷失了。任何人都可以提供一个例子,为什么功能模板专业化很重要和必要?

为什么函数模板不支持偏特化,而类模板支持?底层逻辑是什么?

【问题讨论】:

拜托,我们能避免英美辩论吗?谁在乎是否有人使用s 而其他人使用z :) 我现在看到了它的样子......但之前的问题是“模板规范”。我不在乎 s vs. z。无论如何都删除了评论,因为它不再需要了。 【参考方案1】:

您关于为什么函数不支持偏特化的问题可以回答here。下面的代码展示了如何实现不同的特化。

template<typename T>
bool Less(T a, T b)

    cout << "version 1 ";
    return a < b;

// Function templates can't be partially specialized they can overload instead.
template<typename T>
bool Less(T* a, T* b)

    cout << "version 2 ";
    return *a < *b;


template<>
bool Less<>(const char* lhs, const char* rhs)

    cout << "version 3 ";
    return strcmp(lhs, rhs) < 0;


int a = 5, b = 6;

cout << Less<int>(a, b) << endl;
cout << Less<int>(&a, &b) << endl;
cout << Less("abc", "def") << endl;

【讨论】:

您可以在输出语句的前两个 Less 调用中删除 &lt;int&gt; 你能否说得更清楚一点:函数通常不需要专门化,因为已经存在允许单个符号表示多个函数的重载和一个决策机制来选择(解决)基于哪个函数使用一套规则。 很好的链接。对我来说,那篇文章最重要的一点是,函数特化不会影响 哪个 函数被调用——它们只有在最终模板类型确定之后 才会发挥作用.重载OTOH在“我们应该调用哪个函数”竞赛中引入了一个新的“竞争对手”。 这是一个很好的专业化例子,链接也很棒,但是再看一遍,我看不出为什么函数专业化是重要或必要的,而且我读它的方式是OP真的在问。 (例如,我确信他们的书提供了如何专业化的示例。)【参考方案2】:

我想不出一个例子,自从你问起我就一直在尝试。正如Jagannath 所指出的,long-standing advice 不是专门化函数,而是重载它们或使用特征类(可以专门化,甚至部分专门化)。

例如,如果你需要交换两个项目,那么依赖重载会更好(更可预测和更可扩展):

template<class T>
void f() 
  T a, b;
  using std::swap; // brings std::swap into scope as "fallback"
  swap(a, b); // unqualified call (no "std::") so ADL kicks in
  // also look at boost::swap

以及如何为您的类型编写交换:

// the cleanest way to do it for a class template:
template<class T>
struct Ex1 
  friend void swap(Ex1& a, Ex1& b)  /* do stuff */ 
;

// you can certainly place it outside of the class instead---but in the
// same namespace as the class---if you have some coding convention
// against friends (which is common, but misguided, IMHO):
struct Ex2 ;
void swap(Ex2& a, Ex2& b)  /* do stuff */ 

两者都允许Argument Dependent Lookup (ADL)。

其他函数,例如 stringify/str 或 repr(表示)同样可以是非成员并通过重载利用 ADL:

struct Ex3 
  friend std::string repr(Ex3 const&)  return "<Ex3 obj>"; 
;

std::string repr(bool b)  return b ? "true" : "false"; 

// possible fallback:
template<class T>
std::string repr(T const& v) 
  std::ostringstream out;
  out << v;
  return out.str();

// but in this particular case, I'd remove the fallback and document that
// repr() must be overloaded appropriately before it can be used with a
// particular type; for other operations a default fallback makes sense

换个角度看,如果函数模板可以作为特定实现的注册表,那就太好了,但是由于限制(在当前的 C++ 中,不确定 C++0x 到底是什么带到这里)它们不能像重载或类模板那样工作。

有一种方便但不重要的用法:轻松定义某些特化以放在单独的库中,可能是共享库(.so 或 .dll)。这很方便,因为它需要对通用模板进行最少的更改,但并不重要,因为它对我来说似乎很少见(在野外,而且在我的经验中当然很少见)并且实现者仍然可以使用重载或转发到完全专业化的类模板的非特化方法。

【讨论】:

很好的答案。不过有一件事:我认为您在第三个代码 sn-p 中的朋友声明需要完整的模板 ID 语法 (friend std::string repr&lt;Ex3&gt;(Ex3 const&amp;) ... ) 不是吗? (并且该模板需要在此之前定义或至少声明。) @j_random:不,所有代码都是有效的并且是标准的;从字面上复制到文件中(在适当的#includes 之后)并编译它:codepad.org/v45tt46L。 (顺便说一句,repr 不是模板,但如果是,它也不需要额外的语法,就像 Ex1 的 swap 不需要它一样。) 哎呀,我不知何故错过了您为repr() 提供的就地定义——是的,这是完全有效的。但是,如果您只给出了一个声明,并且预先声明了“后备”模板定义和适当的特化,那么标准要求(14.5.3)您的朋友声明使用 template-id (例如friend std::string repr&lt;Ex3&gt;(...);)或qualified-id(例如friend std::string N::repr(...);,并将模板放入命名空间N)。不幸的是,我发现编译器支持在我的测试中非常可疑...... @j_random:除了我专攻。我找不到它重要或必要的原因,所以我给出了重载的例子。 @j_random:啊,现在我明白你的意思了,但不需要奇怪和尴尬。这很好:template&lt;class T&gt; struct Ex4 friend string repr(Ex4 const&amp;) return "&lt;Ex4&gt;"; ;。它相当直观(实际上与编写静态方法相同,只需使用 'friend')并且不需要更多,这就是我建议默认编写此类重载的方式(与 Ex1 的交换相比)。【参考方案3】:

为了说明为什么函数模板专业化很重要,请考虑std::swap 模板函数。默认情况下,std::swap(x, y) 基本上是这样的:

T temp = x;
x = y;
y = temp;

但这可能效率低下,因为它涉及创建x 的额外副本,并且可以在分配中进行额外的复制。如果x 很大(例如,如果它是一个包含许多元素的std::vector),这尤其糟糕。此外,上述每一行都可能失败并引发异常,从而可能使xy 处于不良、不一致的状态。

为了解决这个问题,许多类都提供了自己的swap 方法(包括std::vector),这些方法可以交换指向其内部数据的指针。这样效率更高,并且可以保证永远不会失败。

但是现在您有一个案例,您可以在某些类型上使用std::swap(x, y),但需要在其他类型上调用x.swap(y)。这很令人困惑,而且对模板不利,因为它们无法以通用、一致的方式交换两个对象。

std::swap 可以专门化,以便在调用特定类型时调用x.swap(y)。这意味着您可以在任何地方使用std::swap,并且(希望)期望它表现良好。

【讨论】:

有点狭窄,因为该标准明确禁止重载 std::swap 而明确允许专门化(这会使您自己的模板处于冷漠状态,因为您不能部分专门化它们)。解决方案是始终使用非成员交换函数(例如定义为朋友)、using std::swap; using-declaration,并使用 swap(a, b) 调用 ADL(参数相关查找)(注意前面没有“std::”)- --对于当前的 C++,这个结果比专门化 std::swap 更灵活、更通用。 啊,对,实际上将std::swap 特化为std::vector 这种方式需要部分特化。啊。【参考方案4】:

基本上,您可以编写针对一般情况以通用方式运行的模板,但仍可以处理特殊情况。使用专业化的一个例子是std::vectorstd::vector&lt;bool&gt; 是一种封装bool 元素的特化,这样它们每个元素只使用一个位,而不是一个字节。 std::vector&lt;T&gt; 对于所有其他类型来说就像一个普通的动态数组。

专业化的更高级用途是元编程。例如,这里有一个示例(来自 Wikipedia),说明如何在编译时使用模板特化来计算阶乘。

template <int N>
struct Factorial 

    enum  value = N * Factorial<N - 1>::value ;
;

template <>
struct Factorial<0> 

    enum  value = 1 ;
;

【讨论】:

是的,vector 不是最好的例子 :) 不是被询问的函数模板特化。 不得不同意 Roger Pate 的观点,抱歉,-1。

以上是关于功能模板专业化的重要性和必要性的主要内容,如果未能解决你的问题,请参考以下文章

app开发中整理需求的重要性

ChemBioDraw在高分子材料领域的重要性

文科生细谈学习Linux系统的重要性

文科生细谈学习Linux系统的重要性

KnockoutJS基础知识

前后端分离的必要性