当模板类型
T
是
int
或
std::string
时,我的模板函数需要进行特殊处理。对特殊类型的
T
我该怎么实现模板特化?
[35.8]
哈?你能提供一个具体的模板特化的例子吗?
Updated!
[35.9]
但是模板函数的大部分代码是相同的,是否有办法实现模板特化并且不
用重复复制所有的源代码?
[35.10]
所有这些模板和模板特化都会降低程序执行速度,对不对?
[35.11]
因此
模板重载了函数,对不对?
[35.12]
为什么不能分开模板的声明和定义,把定义放到
.cpp
文件中?
[35.13]
如何避免模板函数的链接错误?
[35.14]
如何使用
C + +
的关键字
export
来避免模板链接错误?
Updated!
[35.15]
如何避免模板类的链接错误?
[35.16]
为什么我收到链接错误
,当我使用模板友元的时候?
[35.17]
怎么理解
这些繁琐的模板错误信息?
[35.18]
当模板派生类使用一个继承自
模板基类的
嵌套类型时,为什么出错?
New!
[35.19]
当模板派生类
使用使用
一个继承自
模板基类的
成员变量时
,为什么出错?
Updated!
[35.20]
前一个问题可以暗伤我?难道编译器默认地产生错误代码?
Updated!
模板像是甜饼切割器,指定如何切割
cookies
让他们看起来大致相同(虽然
Cookie
由各种面团来制作,但是他们都会有相同的基本形状)。同样,类模板是描述如何建立一个类族,让所有的类看起来是基本相同;函数模板描述如何建立一个外观类似的函数族。
类模板通常用于构建类型安全的容器(
although this only scratches
the surface for how they can be used
)。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
class Array {
public:
Array(int len=10) : len_(len), data_(new int[len]) { }
~Array() { delete[] data_; }
int len() const { return len_; }
const int& operator[](int i) const { return data_[check(i)]; }
←
subscript operators often come in pairs
int& operator[](int i) { return data_[check(i)]; }
←
subscript operators often come in pairs
Array(const Array&);
Array& operator= (const Array&);
private:
int len_;
int* data_;
int check(int i) const
{ if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
return i; }
对于浮点数数组,字符数组,
std::string
数组
,
std::string
数组的
数组
等,反复重复上述步骤将很冗长乏味。
// This would go into a header file such as "
Array.h
"
template<typename T>
class Array {
public:
Array(int len=10) : len_(len), data_(new T[len]) { }
~Array() { delete[] data_; }
int len() const { return len_; }
const T& operator[](int i) const { return data_[check(i)]; }
T& operator[](int i) { return data_[check(i)]; }
Array(const Array<T>&);
Array<T>& operator= (const Array<T>&);
private:
int len_;
T* data_;
int check(int i) const
{ if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
return i; }
与
模板函数
不同
,
模板类(实例化模板)在实例化时需要指明相关参数:
int
main()
Array<int> ai;
Array<float> af;
Array<char*> ac;
Array<std::string> as;
Array< Array<int> > aai;
注意最后一个例子中的两个“
>
”之间的空格符。如果没有这个空格符,编译器会看到一个“
>>
”(右移位)标记,而不是两个“
>
”。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
int i,j;
/*...*/
swap(i,j);
// Instantiates a swap for
int
float a,b;
/*...*/
swap(a,b);
// Instantiates a swap for
float
char c,d;
/*...*/
swap(c,d);
// Instantiates a swap for
char
std::string s,t;
/*...*/
swap(s,t);
// Instantiates a swap for
std::string
注:“模板函数”是一个“函数模板”的实例化形态。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
当你调用一个函数模板时,编译器试图推断模板类型。大部分情况下,编译器可以成功的做到这一点,但有时你可能想要帮助编译器推断出正确的类型
-
要么是因为它不能推断出模板类型,或者是因为它会推断出错误类型。
例如,你可能会调用一个函数模板没有模板指定的参数类型,或者你可能想让编译器在选择正确的函数模板之前,迫使它对参数做一些转换(
promotions
)。在这些情况下,你需要明确地告诉编译器应该调用函数的模板哪个实例化。
下面是一个示例函数模板,模板参数
T
没有出现在函数的参数列表中。在这种情况下,
编译器无法推断出模板参数类型在函数被调用时。
template<
typename T>
void f()
若要调用该函数把
T
作为
int
或
std::string
,你可以这样做:
#include <string>
void
sample(
)
f<int>();
// type
T
will be
int
in this call
f<std::string>();
// type
T
will be
std::string
in this call
这里是另一个函数,它的模板参数出现在函数的正式参数列表中(也就是说,编译器
可以
根据实际参数的类型推导出模板类型):
template<
typename T>
void g(T x)
现在如果你想强制实行参数转换,在编译器推断模板类型之前,你可以使用上述技术。例如,如果你只是简单调用
g(42)
,你会得到
g<int>
(
42
),但如果你想传递
42
给
g<long>
(),你可以这样做:
g<long>
(
42
)。(当然你也可以明确地转换参数,如可以
g
(
long
(
42
)),甚至
g
(
42L
),当然如果这样的话本例子就没有什么意义了。)
同样,如果你调用
g
(“
xyz
”),你最终会调用
g<char*>
(
char*
),但如果你想调用
std::string
版本
g<>
(),你可以这样
g<std::string>
(
”
xyz
“
)。(同样你也可以转换参数,例如
g(std::string(“xyz”)
,不过那将是另一回事。)
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
不要
与“一般性
(generality)
”混淆(“一般性
(generality)
”这只是避免过于具体的解决方案),“泛型”是指类模板。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
在展示如何做到这一点之前,让我们确保你不会搬起石头砸自己的脚。对于用户来说是否该函数的行为不同?换言之,是否可以观察到的行为有实质性的不同?如果是这样,你可能是在自找苦吃,你可能迷惑用户
--
你最好使用不同名称的函数
--
不要使用模板,不要使用重载。例如,如果接受
int
类型的代码要插入一些东西到容器并且对结果排序,但接受
std::string
类型的代码要从容器中删除东西并且不对结果排序,这两个函数不应该是可以重载的函数对
--
他们可以观察的行为是不同的,所以他们应该有不同的函数名称。
但是,如果该函数的可观察到的行为是一致的,对于所有
T
类型仅仅局限在各自实现细节上的不同,那么就请继续读下去。让我们看看这方面的一个例子(仅仅是概念上,不是
C + +
代码):
template<
typename T>
void foo(const T& x)
switch (typeof(T)) {
←
conceptual only; not C++
case int:
...
←
implementation details when
T
is
int
break;
case std::string:
...
←
implementation details when
T
is
std::string
break;
default:
...
←
implementation details when
T
is neither
int
nor
std::string
break;
解决上述问题的办法就是
是
通过模板特化。不要使用
switch
语句,你需要把代码分解成单独的函数。第一个函数是默认的情况
--
当
T
是
int
或
std::string
以外的任何其他类型时候的代码:
template<
typename T>
void foo(const T& x)
...
←
implementation details when
T
is neither
int
nor
std::string
下一步是两个特例,第一个是
int
特例
的代码:
template
<>
void foo<int>(const int& x)
...
←
implementation details when
T
is
int
接着是
std::string
特例
的代码:
template
<>
void foo<std::string>(const std::string& x)
...
←
implementation details when
T
is
std::string
好啦,大功告成!编译器将自动选择正确的特例实现根据所使用的
T
的类型。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
[
最近增加有关
std::numeric_limits<>::max_digits10
注意事项。感谢
Alessandro Gentilini
(in 1/09).
。点击
这里
到最近修改过的下一个
FAQ
。
]
下面我个人使用
模板特化
的几种常见情况是字符串化。我通常使用模板,
将
不同类型的对象字符串化
,但通常需要字符串
化某些
特定的类型,例如当字符串化
布尔变量的时候,我喜欢用
“true”
与
“false”
来代替
“1”
和
“0”
,所以当
T
是布尔类型时,我使用
std::boolalpha
。此外,我喜欢浮点输出包含所有的数字(这样我就可以看得很小的差异,等等),因此当
T
是一个浮点类型时候,我使用
std::setprecision
。最终的结果通常如下所示:
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <limits>
template<typename T> inline std::string stringify(const T& x)
std::ostringstream out;
out << x;
return out.str();
template<> inline std::string stringify<bool>(const bool& x)
std::ostringstream out;
out << std::boolalpha << x;
return out.str();
template<> inline std::string stringify<double>(const double& x)
const int sigdigits = std::numeric_limits<double>::digits10;
// or perhaps
std::numeric_limits<double>::max_digits10
if that is available on your compiler
std::ostringstream out;
out << std::setprecision(sigdigits) << x;
return out.str();
template<> inline std::string stringify<float>(const float& x)
const int sigdigits = std::numeric_limits<float>::digits10;
// or perhaps
std::numeric_limits<float>::max_digits10
if that is available on your compiler
std::ostringstream out;
out << std::setprecision(sigdigits) << x;
return out.str();
template<> inline std::string stringify<long double>(const long double& x)
const int sigdigits = std::numeric_limits<long double>::digits10;
// or perhaps
std::numeric_limits<long_double>::max_digits10
if that is available on your compiler
std::ostringstream out;
out << std::setprecision(sigdigits) << x;
return out.str();
从概念上来讲他们都做同样的事情:把参数字符串化。这意味着可观察的行为是一致的,因此特化不会迷惑用户。但对于
bool
和浮点类型,细节的实现略有不同,因此模板特化是一个好的解决方法。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
... common code that works for all
T
types ...
switch (typeof(T)) {
←
conceptual only; not C++
case int:
... small amount of code used only when
T
is
int
...
break;
case std::string:
... small amount of code used only when
T
is
std::string
...
break;
default:
... small amount of code used when
T
is neither
int
nor
std::string
...
break;
... more common code that works for all
T
types ...
如果盲目地跟从
模板特化
FAQ
的建议,你最终将需要重复
switch
语句之前和之后的所有代码。两全其美的方式
—
既不重复相同代码又可以实现
T
的特定代码,是分离
switch
语句到一个单独的函数
foo_part
(),并
使用模板特殊化
:
template<typename T> inline void foo_
part(
const T& x)
... small amount of code used when
T
is neither
int
nor
std::string
...
template<> inline void foo_part<int>(const int& x)
... small amount of code used only when
T
is
int
...
template<> inline void foo_part<std::string>(const std::string& x)
... small amount of code used only when
T
is
std::string
...
主要的
foo
()函数是一个简单的模板
-
没有特化。请注意,
switch
语句已经被替换为
foo_part
()调用:
template<typename T>
void foo(const T& x)
... common code that works for all
T
types ...
foo_part(x);
... more common code that works for all
T
types ...
正如你所看到的,
foo
()的函数体本身并没有任何特殊,这一切都会自动的被调用。编译器自动生成的基于
T
类型
的
foo
(),并会生成正确的
foo_part
函数,根据实际编译时的
X
的参数类型。合适的
foo_part
的特化会被实例化。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
这与实现代码的质量有关,结果可能会有所不同。但是不会有任何降低。模板可能会些微影响编译速度,但一旦类型在编译时被确定,它通常会生成和非模板函数(包括内联展开等)一样快的代码。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
template<typename T> void foo(T* x)
{ std::cout << "foo<" << typeid(T).name() << ">(T*)\n"; }
void foo(int x)
{ std::cout << "foo(int)\n"; }
void foo(double x)
{ std::cout << "foo(double)\n"; }
int main()
foo(42);
// matches
foo(int)
exactly
foo(42.0);
// matches
foo(double)
exactly
foo("abcdef");
// matches
foo<T>(T*)
with
T
=
char
return 0;
在这个例子中,
在
main()
函数中第一或第二次调用
foo
不是对
foo<T>
的调用,因为无论
42
还是
42.0
都没有提供给编译器的任何信息来推断
。然而第三个调用,包括
foo<T>
并且
T = char
,因此它会调用
foo<T>
。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
模板是不是一个类或函数。
模板是一个“模式”,编译器用来生成的相似的
类
或者
函数
。
为了让编译器生成的代码,它必须同时看到模板的定义(不只是声明)和特定类型
/
任何用于
“fill in”
模板的类型。例如,如果你想使用一个
foo<int>
,编译器必须同时看到
foo
模板和你要调用具体的
foo<int>
。
编译器可能不记得另外一个
.cpp
文件的细节,当编译其他
.cpp
文件的时候。它可以
,但大多数都没有,如果你正在阅读本
FAQ
,它几乎肯定不会。顺便说一句,这就是所谓的“独立编译模型”。
现在,基于这些事实,下面是一个范例,它表明为什么是这个样子。假设你有一个这样的模板
Foo
声明:
template<
typename T>
class Foo {
public:
Foo();
void someMethod(T x);
private:
类似地,模板成员函数的定义:
template<typename T>
Foo<T>::Foo()
template<typename T>
void Foo<T>::someMethod(T x)
现在,假设在文件
Bar.cpp
的一些代码要使用
foo<int>
:
// Bar.cpp
void blah_blah_
blah(
)
Foo<int> f;
f.someMethod(5);
显然,某人某地将不得不调用“模式”的构造函数,和
someMethod
()函数以及做
T
为
int
的实例化。但是,如果你把构造函数和
someMethod
()的定义放到文件
Foo.cpp
,当编译
Foo.cpp
时,编译器将看到模板代码;当编译
Bar.cpp
时,编译器将看到
foo<int>
。但任何时候决不会同时看到模板代码和
foo<int>
。因此,通过上面的
2
号规则,它根本不会产生
foo <int>::someMethod
()的代码。
写给专家们的话:
很明显我对以上内容作了简化。这是有意为之,所以请不要大声抱怨。
如果你知道
.cpp
文件和编译单元的差别,类模板和模板类的差别,模板其实不只是美化的宏等,请不要抱怨:这个问题
/
解答不是为你而设。我简化它是为了新手能够“理解它”,即使这样可能会冒犯一些专家。
提醒:
欲
知解决
方案,
请阅读下面得
两个
FAQ
s
。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
如果你编译和(试图)链接这两个
.cpp
文件,大多数编译器将生成链接错误。有三种的解决方案。第一个解决方案是物理上在
.h
文件中定义,即使它不是一个内联函数。这种解决办法可能(或可能不会!)造成重大代码膨胀,意味着可执行文件的大小可能会
显显著
增加(或者,如果你的编译器足够聪明,可能不会这么做)。
另一个解决办法是保留定义在
.cpp
文件中,只添加行
template void foo<int>()
到
.cpp
文件:
// File "foo.cpp"
#include <iostream>
#include "foo.h"
template<typename T> void
foo(
)
std::cout << "Here I am!\n";
template void foo<int>();
如果你不能修改
foo.cpp
,只需创建一个新的
.cpp
文件,例如
foo-impl.cpp
如下:
// File "foo-impl.cpp"
#include "foo.cpp"
template void foo<int
>(
);
请注意,
foo-impl.cpp
文件包含
.cpp
文件,而不是
.h
文件。如果你觉着这样很乱,跳个踢踏舞,想想堪萨斯,跟着我重复,“我要这么做即使它很混乱。”
你需要信任我。如果不信任或者致使好奇,
前面
的
FAQ
给出了理由。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
[
最近修改了一个错误,修改了最后一段的
NO_EXPORT_KEYWORD
为
USE_EXPORT_KEYWORD
。感谢
Luís Bruno
(in 9/06)
。点击
这里
到最近修改过的下一个
FAQ
。
]
C + +
关键字
export
是设计用来消除
包含
一个
模板定义
(无论是在头文件中或通过实现文件中)的需要。但是,在写这篇文章时,支持此功能的唯一的知名编译器,是
Comeau C + +
。
export
关键字未来还是个未知数。说句公道话,一些编译器厂商表示他们可能永远不会实现它,而
C + +
标准委员会已决定大家自己定夺。
在不支持关键字
export
的编译器上,如果你希望你的代码可以通过编译,并且还希望能够有效利用支持
export
关键字的编译器。你可以这样定义模板头文件:
// File Foo.h
template<typename T>
class Foo {
#ifndef USE_EXPORT_KEYWORD
#include "Foo.cpp"
#endif
并
定义非
内联函数的源代码文件如下:
// File Foo.cpp
#ifndef USE_EXPORT_KEYWORD
#define export
/*nothing*/
#endif
export template<typename T
> ...
然后,如果
/
当你的编译器支持
export
关键字的时候,并且因为某些原因你想利用该功能,只要定义符号
USE_EXPORT_KEYWORD
即可。
要诀就是,你现在可以开发程序,
好像你的编译器已经实现了
export
关键字。如果
/
当你的编译器真正支持该关键字的时候,只需要定义
USE_EXPORT_KEYWORD
标志,重新编译,马上你就可以利用该功能。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
如果你编译和(试图)链接这两个
.cpp
文件,大多数编译器将生成链接错误。有三种的解决方案。第一个解决方案是物理上在
.h
文件中定义,即使它不是一个内联函数。这种解决办法可能(或可能不会!)造成重大代码膨胀,意味着可执行文件的大小可能会
显显著
增加(或者,如果你的编译器足够聪明,可能不会这么做)。
另一个解决办法是保留定义在
.cpp
文件中,只添加行
template class Foo<int>;
到
.cpp
文件:
// File "Foo.cpp"
#include <iostream>
#include "Foo.h"
...definition of Foo<T>:
:f
() is unchanged -- see above...
...definition of Foo<T>::g() is unchanged -- see above...
template class Foo<int>;
如果你不能修改
foo.cpp
,只需创建一个新的
.cpp
文件,例如
foo-impl.cpp
如下:
// File "Foo-impl.cpp"
#include "Foo.cpp"
template class Foo<int>;
请注意,
foo-impl.cpp
文件包含
.cpp
文件,而不是
.h
文件。如果你觉着这样很乱,跳个踢踏舞,想想堪萨斯,跟着我重复,“我要这么做即使它很混乱。”
你需要信任我。如果不信任或者致使好奇,
前面
的
FAQ
给出了理由。
如果你使用
Comeau C++
,你可能使用
export
关键字实现类似功能。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
public:
Foo(const T& value = T());
friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs);
friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x);
private:
T value_;
当然在某个地方我们会用到模板:
int
main()
Foo<int> lhs(1);
Foo<int> rhs(2);
Foo<int> result = lhs + rhs;
std::cout << result;
当然,在某个地方需要定义各成员和
友元
函数:
template<typename T>
Foo<T>::Foo(const T& value = T())
: value_(value)
template<typename T>
Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)
{ return Foo<T>(lhs.value_ + rhs.value_); }
template<typename T>
std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
{ return o << x.value_; }
一个潜在问题是编译器如何理解类声明中的
friends
行。在看到
friends
行的时候,它还不知道友元函数本身也是模板,它假定他们不是模板函数,就像下面这样:
Foo<int> operator+ (const Foo<int>& lhs, const Foo<int>& rhs)
{ ... }
std::ostream& operator<< (std::ostream& o, const Foo<int>& x)
{ ... }
当你调用运算符
+
或运算符
<<
的时候,这种假设导致编译器生成一个对非模板函数的调用,但是链接器会给你一个
“
未定义的外部函数
”
错误,因为你从来没有真正的定义这些非模板函数。
解决的办法是在编译器
编译类体的
时候,让编译器知道运算符
+
和运算符
<<
本身是模板。有几种方法可以做到这一点;一个简单的方法是在定义函数模板类
Foo
的时候预先声明模板友元:
template<
typename T> class Foo;
// pre-declare the template class itself
template<typename T> Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs);
template<typename T> std::ostream& operator<< (std::ostream& o, const Foo<T>& x);
public:
Foo(const T& value = T());
friend Foo<T> operator+ <> (const Foo<T>& lhs, const Foo<T>& rhs);
friend std::ostream& operator<< <> (std::ostream& o, const Foo<T>& x);
private:
T value_;
这些写法将有助于编译器更好地了解友元函数。值得一提的是,它会发现友元函数本身是模板。这消除了混乱。
另一种方法是在类中同时声明和定义该友元函数。例如:
#include <iostream>
template<typename T>
class Foo {
public:
Foo(const T& value = T());
friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)
friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
private:
T value_;
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
这里有一个免费工具,
可以
转换
错误信息
便于
理解
。在撰写本文的时候,它工作用于下列编译器:
Comeau C +
,
Intel C + +
,
CodeWarrior C + +
,
gcc
,
Borland C + +
,
Microsoft Visual C + +
和
EDG C + +
。
Here's a free tool
that
transforms error messages into
something more understandable
. At the time of this writing, it works with
the following compilers: Comeau C++, Intel C++, CodeWarrior C++, gcc, Borland
C++, Microsoft Visual C++, and EDG C++.
这里有一个例子,下面是一些原始的
gcc
的错误信息:
rtmap.cpp: In function `int main()':
rtmap.cpp:19: invalid conversion from `int' to `
std::_Rb_tree_node<std::pair<const int, double> >*'
rtmap.cpp:19: initializing argument 1 of `std::_Rb_tree_iterator<_Val, _Ref,
_Ptr>::_Rb_tree_iterator(std::_Rb_tree_node<_Val>*) [with _Val =
std::pair<const int, double>, _Ref = std::pair<const int, double>&, _Ptr =
std::pair<const int, double>*]'
rtmap.cpp:20: invalid conversion from `int' to `
std::_Rb_tree_node<std::pair<const int, double> >*'
rtmap.cpp:20: initializing argument 1 of `std::_Rb_tree_iterator<_Val, _Ref,
_Ptr>::_Rb_tree_iterator(std::_Rb_tree_node<_Val>*) [with _Val =
std::pair<const int, double>, _Ref = std::pair<const int, double>&, _Ptr =
std::pair<const int, double>*]'
E:/GCC3/include/c++/3.2/bits/stl_tree.h: In member function `void
std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::insert_unique(_II,
_II) [with _InputIterator = int, _Key = int, _Val = std::pair<const int,
double>, _KeyOfValue = std::_Select1st<std::pair<const int, double> >,
_Compare = std::less<int>, _Alloc = std::allocator<std::pair<const int,
double> >]':
E:/GCC3/include/c++/3.2/bits/stl_map.h:272: instantiated from `void std::map<_
Key, _Tp, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _Input
Iterator = int, _Key = int, _Tp = double, _Compare = std::less<int>, _Alloc = st
d::allocator<std::pair<const int, double> >]'
rtmap.cpp:21: instantiated from here
E:/GCC3/include/c++/3.2/bits/stl_tree.h:1161: invalid type argument of `unary *
以下是经过过滤的错误信息(注:你可以配置工具让它显示更多的信息,下面输出的设置是剪裁信息到最少):
rtmap.cpp: In function `int main()':
rtmap.cpp:19: invalid conversion from `int' to `iter'
rtmap.cpp:19: initializing argument 1 of `iter(iter)'
rtmap.cpp:20: invalid conversion from `int' to `iter'
rtmap.cpp:20: initializing argument 1 of `iter(iter)'
stl_tree.h: In member function `void map<int,double>::insert_unique(_II, _II)':
[STL Decryptor: Suppressed 1 more STL standard header message]
rtmap.cpp:21: instantiated from here
stl_tree.h:1161: invalid type argument of `unary *'
m.insert(make_pair(values[i], pow(values[i], .5)));
valmap::iterator it = 100;
// error
valmap::iterator it2(100);
// error
m.insert(1,2);
// error
return 0;
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
public:
class Xyz { ... };
←
type nested in class
B<T>
typedef int Pqr;
←
type nested in class
B<T>
template<typename T>
class D : public B<T> {
public:
void g()
Xyz x;
←
bad (even though some compilers erroneously (temporarily?) accept it)
Pqr y;
←
bad (even though some compilers erroneously (temporarily?) accept it)
这可能会让你很伤脑筋,最好坐下来听我讲。
在函数
D<T>::g()
内,名字
xyz
和
Pqr
不依赖于模板参数
T
,所以他们被称作为
nondependent
名字。另一方面
B<T>
依赖模板参数
T
,因此
B<T>
称作
dependent
名字
。
规则是这样的:当查找
nondependent
名字(比如
Xyz
和
Pqr
)的时候,编译器不会查找
dependent
基类(如
B <T>
中
)。因此,编译器不知道他们甚至还存在,更不用说知道它们也是类型。
这时,程序员有时会添加前缀
B <T>
::,例如:
template<typename T>
class D : public B<T> {
public:
void g()
B<T>::Xyz x;
←
bad (even though some compilers erroneously (temporarily?) accept it)
B<T>::Pqr y;
←
bad (even though some compilers erroneously (temporarily?) accept it)
可惜这也行不通,因为这些名字(你准备好了吗?坐下来?)不一定是类型。
"
哈
?!?"
?
"
不是类型
?!?"
?。“太搞了吧!任何傻瓜都可以看到他们是类型
;
只要看上一眼!”,你抗议。抱歉,事实是,他们可能不是类型。原因是,有可能是
B<T>
的特化,假设
B<Foo>
,其中
B <Foo>::Xyz
是一个数据成员。由于这种潜在的特化,编译器不能假设
B<T>::Xyz
是一个类型,直到它知道
T
。解决方案是通过
typename
关键字提示编译器:
template<typename T>
class D : public B<T> {
public:
void g()
typename B<T>::Xyz x;
←
good
typename B<T>::Pqr y;
←
good
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
这可能会让你很伤脑筋,最好坐下来听我讲。
在函数
D<T>::g()
内,名字
f
不依赖于模板参数
T
,所以他们被称作为
nondependent
名字。另一方面
B<T>
依赖模板参数
T
,因此
B<T>
称作
dependent
名字
。
规则是这样的:当查找
nondependent
名字(比如
f
)的时候,编译器不会查找
dependent
基类(如
B <T>
中
)。
这并不意味着继承不起作用。类
D <int>
是仍然
继承自类
B <int>
,编译器仍然让你可以隐式的做
is- a
转换(例如,
D<int>*
到
B <int> *
),动态绑定仍然
有效当虚函数
被调用时,等等。但有一个如何查找名称的问题。
替代方案:
改变的
f()
的调用为
this->f()
。由于在模板中
this
指针一直是隐式实现的,
this->f()
要依赖查找,因此推迟到模板实例化时,此时
所有基类都会
被查找。
在调用
f()
之前,插入
using B<T>::f;
语句。
改变的
f()
的调用为
B <T>::f()
。
但是请注意,如果
f
()是虚函数,这可能没有给你想要的东西,因为它禁止了虚函数带调用机制。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ
由于
non-dependent
类型
and
non-dependent
成员
不会在
dependent
模板在基础类中搜索,编译器将搜索封闭范围,比如封闭名字空间。这可能会导致它在你没有意识到的情况下(!)
做错误
的事情。
class Xyz { ... };
←
global ("namespace scope") type
void f() { }
←
global ("namespace scope") function
template<typename T>
class B {
public:
class Xyz { ... };
←
type nested in class
B<T>
void f() { }
←
member of class
B<T>
template<typename T>
class D : public B<T> {
public:
void g()
Xyz x;
←
suprise: you get the global
Xyz
!!
f(
);
←
suprise: you get the global
f
!!
D<T>::g()
内的
Xyz
和
f
将被解析为全局变量,而不是
继承自类
B <T>
,这恐怕不是你的真正意图。
别埋怨我没有警告过你。
[
Top
|
Bottom
|
Previous section
|
Next section
|
Search the FAQ