学习C++,来到函数模板。该章提到了模板专业化。C++ - 函数模板专门化的目的是什么?何时使用它?
template <> void foo<int>(int);
void foo(int);
为什么专注时,你可以使用第二个?我认为模板是普遍化的。当您只需使用常规功能时,为特定数据类型专门设定功能有什么意义?
显然,模板专门化存在的原因。什么时候应该使用它?我读过Sutter的"Why not Specialize..."文章,但我需要更多的外行人版本,因为我只是在学习这些东西。
学习C++,来到函数模板。该章提到了模板专业化。C++ - 函数模板专门化的目的是什么?何时使用它?
template <> void foo<int>(int);
void foo(int);
为什么专注时,你可以使用第二个?我认为模板是普遍化的。当您只需使用常规功能时,为特定数据类型专门设定功能有什么意义?
显然,模板专门化存在的原因。什么时候应该使用它?我读过Sutter的"Why not Specialize..."文章,但我需要更多的外行人版本,因为我只是在学习这些东西。
主要区别在于,第一种情况是您为编译器提供了特定类型的实现,而第二种情况是您提供了不相关的非模板函数。
如果您总是让编译器推断这些类型,编译器会优先选择非模板函数,而编译器将调用free函数而不是模板,因此提供了一个与非模板化函数匹配的模板函数在大多数情况下,参数将具有相同的专业化效果。
在另一方面,如果在任何地方,你提供的模板参数(而不是让编译器推断),那么它只会调用通用模板,可能产生意想不到的结果:
template <typename T> void f(T) {
std::cout << "generic" << std::endl;
}
void f(int) {
std::cout << "f(int)" << std::endl;
}
int main() {
int x = 0;
double d = 0.0;
f(d); // generic
f(x); // f(int)
f<int>(x); // generic !! maybe not what you want
f<int>(d); // generic (same as above)
}
如果您已经为该模板的int
提供了专门化,最后两个调用将调用该专用而不是通用模板。
关于'f
还有几个不同之处。过度使用限定名称不能很好地发挥作用,除非在同一范围内声明。扩展标准库函数需要专业化,而不是过载。随着更多的过载,ADL的浮躁往往会增加。 – Potatoswatter 2010-09-11 22:07:48
虽然如果你的接口声明有一个你可以调用的函数(-template)'f',那么你不能依赖类似'f
我个人认为没有专门的功能模板的好处。通过不同的函数模板或非模板函数重载它可以说是优越的,因为它的处理更直观,并且总体上更强大(通过重载模板有效地实现了模板的部分特化,即使在技术上它被称为部分排序)。
Herb Sutter写了一篇文章Why not specialize function templates?,他不鼓励使用特殊函数模板,而是倾向于重载或编写函数模板,以便它们只是转发到类模板的静态函数,并改为专用类模板。
由于函数模板speciailizations不参与重载过程,是'功能模板专业化'的好处,因为它加快了重载解决方案 – Chubsdad 2010-09-12 02:24:28
@Chubsdad:我永远不会试图优化编译时间。应该以更高的理由作出决定,而不是编译器处理代码的时间。 – 2010-09-12 10:46:35
您应该尝试优化编译器本身:) – 2010-09-12 11:43:17
我觉得这很重要。你可以像使用虚拟方法一样使用它。虚拟方法没有意义,除非其中一些是专门的。我用它来区分简单类型(int,short,float)和对象,对象指针和对象引用。 一个例子是通过调用对象serialize/unserialize方法处理对象的serialize/unserialize方法,而简单类型应该直接写入流。
虚拟方法不能作为模板。 – Potatoswatter 2010-09-11 21:30:06
@Potato:我认为他正在画一个平行线。如果你声明一个虚函数,但是没有派生类超载它,虚拟类型就会浪费。可以肯定的是,它并不是一个很好的平行线。 – 2010-09-11 22:55:00
模板特殊化的一种情况是不能用于重载的情况,即模板元编程。以下是库中的实际代码,它在编译时提供一些服务。
namespace internal{namespace os{
template <class Os> std::ostream& get();
struct stdout{};
struct stderr{};
template <> inline std::ostream& get<stdout>() { return std::cout; }
template <> inline std::ostream& get<stderr>() { return std::cerr; }
}}
// define a specialization for os::get()
#define DEFINE_FILE(ofs_name,filename)\
namespace internal{namespace os{ \
struct ofs_name{ \
std::ofstream ofs; \
ofs_name(){ ofs.open(filename);} \
~ofs_name(){ ofs.close(); delete this; } \
}; \
template <> inline std::ostream& get<ofs_name>(){ return (new ofs_name())->ofs; } \
}} \
using internal::os::ofs_name;
这看起来不正确。 'stdout'是一个对象(它可以是一个宏,它几乎毁了一切),所以它不应该与参数类型'class Os'匹配。如果有的话,你需要'template < FILE * > std :: ostream&get();' – Potatoswatter 2010-09-11 22:05:25
@Patatoswatter:它的内部(命名空间内部)。稍后,它将以适当的名称进入图书馆的公共(而非全局)名称空间。模板
同名多个重载做类似事情。专业化做完全相同的事情,但在不同的类型。重载具有相同的名称,但可以在不同的作用域中定义。一个模板仅在一个范围内声明,专业化声明的位置是微不足道的(尽管它必须位于封闭名称空间的范围内)。
例如,如果你扩展std::swap
支持你的类型,必须通过专业化这样做,因为功能被命名为std::swap
,不能简单地swap
,并在<algorithm>
的功能将是非常合适的专门称呼其为::std::swap(a, b);
。同样,对于可能在名称空间中混叠的任何名称:一旦您限定名称,调用函数可能会变得“更难”。
范围问题进一步被argument-dependent lookup混淆。通常可能会发现重载,因为它定义在它的一个参数的类型附近。 (例如,作为静态成员函数。)这与模板专业化的发现方式完全不同,只需查找模板名称,然后在选择模板作为目标后查找显式专业化的通话。
ADL的规则是标准中最混乱的部分,所以我更愿意明确地专注于避免依赖它的原则。
那么,正式的重载确实只发生在一个范围内。如果两个函数声明在不同的作用域中,那么它们不会每个都超载。但我想说的实际观点是 - 假设你有一个'template
我认为一般模式是通过编写'{using std :: swap;交换(a,b); }'它可以通过ADL和'std :: swap'的特化找到两个声明。我觉得过载更容易。每个人都应该这样做,因为他认为合适:) – 2010-09-12 02:01:35
@Johannes:你有一个可行的解决方案,但它不是标准指定的。关键是为''
也许带上地址:'&foo vs(void(...))foo'或专门的返回类型? –
Anycorn
2010-09-11 20:41:19
[功能模板专业化的重要性和必要性]的可能的副本(http://stackoverflow.com/questions/2197141/function-template-specialization-importance-and-necessity) – jamesdlin 2010-09-11 20:49:02