简体中文版翻译:Zhiguo Zhang
模板像是甜饼切割器,指定如何切割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,它的行为像一个整数数组:
// This would go into a header file such as "Array.h"
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
]
考虑下面函数,交换两个整型参数:
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
如果我们还要交换浮点数,长整形,字符串,集合,和文件系统等,我们就会疲于编写除了类型不同的相似的编码行。重复是电脑理想的工作,因此要用函数模板:
template<typename T>
void swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
对给定的类型每次我们使用swap()的时候,编译器将根据上述定义,并自动产生另外一个“模板函数”作为上述函数模板的实例化。例如:
int main()
{
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
...
}
注:“模板函数”是一个“函数模板”的实例化形态。
[ 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
}
这里是另一个函数,它的模板参数出现在函数的正式参数列表中(也就是说,编译器可以根据实际参数的类型推导出模板类型):
template<typename T>
void g(T x)
{
...
}
现在如果你想强制实行参数转换,在编译器推断模板类型之前,你可以使用上述技术。例如,如果你只是简单调用g(42),你会得到g<int>(42),但如果你想传递42给g<long>(),你可以这样做: g<long>(42)。(当然你也可以明确地转换参数,如可以g(long(42)),甚至g(
同样,如果你调用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
]
换句话说,“类模板”。
参数化类型是一个类型,是参数化的类型或者值。
[ 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
break;
default:
... ← implementation details when T is neither int nor
break;
}
}
解决上述问题的办法就是是通过模板特化。不要使用switch语句,你需要把代码分解成单独的函数。第一个函数是默认的情况--当 T是int或std::string以外的任何其他类型时候的代码:
template<typename T>
void foo(const T& x)
{
... ← implementation details when T is neither int nor
}
下一步是两个特例,第一个是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
}
好啦,大功告成!编译器将自动选择正确的特例实现根据所使用的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::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::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::ostringstream out;
out << std::setprecision(sigdigits) << x;
return out.str();
}
从概念上来讲他们都做同样的事情:把参数字符串化。这意味着可观察的行为是一致的,因此特化不会迷惑用户。但对于bool和浮点类型,细节的实现略有不同,因此模板特化是一个好的解决方法。
[ Top | Bottom | Previous section
| Next section | Search the FAQ
]
是。
例如,假设你的模板函数有很多共同的代码,与类型T相关的特定代码相对很少(仅仅是概念展示;不是C + +):
template<typename T>
void foo(const T& x)
{
... 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
break;
default:
... small amount of code used when T is neither int nor
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
}
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
}
主要的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
]
是也不是。
函数模板参与重载函数的名称解析,但规则是不同的。对于模板重载,类型需要完全匹配。如果类型不完全匹配,类型不会被转换,函数模板从可行的函数集合中被排除。这就是所谓的“SFINAE”- Subsitution Failure Is Not An Error。例如:
#include <iostream>
#include <typeinfo>
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(42.0); // matches
foo("abcdef"); // matches
return 0;
}
在这个例子中, 在main()函数中第一或第二次调用foo不是对foo<T>的调用,因为无论42还是42.0都没有提供给编译器的任何信息来推断 。然而第三个调用,包括foo<T>并且T = char,因此它会调用foo<T>。
[ Top | Bottom | Previous section
| Next section | Search the FAQ
]
如果你想知道的是只是如何解决这种情况,请阅读下面得两个
FAQs。但是,为了理解要那样,首先接受这些事实:
现在,基于这些事实,下面是一个范例,它表明为什么是这个样子。假设你有一个这样的模板Foo声明:
template<typename T>
class Foo {
public:
Foo();
void someMethod(T x);
private:
T x;
};
类似地,模板成员函数的定义:
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文件和编译单元的差别,类模板和模板类的差别,模板其实不只是美化的宏等,请不要抱怨:这个问题/解答不是为你而设。我简化它是为了新手能够“理解它”,即使这样可能会冒犯一些专家。
[ Top | Bottom | Previous section
| Next section | Search the FAQ
]
当编译模板函数的.cpp文件的时候告诉C + +编译器应该使用哪个实例。
例如,考虑foo.h头文件包含以下模板函数声明:
// File "foo.h"
template<typename T>
extern void foo();
现在假设文件foo.cpp实际上定义的模板函数:
// File "foo.cpp"
#include <iostream>
#include "foo.h"
template<typename T>
void foo()
{
std::cout << "Here I am!\n";
}
假设文件main.cpp中使用这个模板函数通过调用foo<int>():
// File "main.cpp"
#include "foo.h"
int main()
{
foo<int>();
...
}
如果你编译和(试图)链接这两个.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文件得手告诉你的C + +编译器应该使用哪个模板实例。(如果你已经阅读以前的问题,答案是完全一样的,所以你也许可以跳过此答案。)
作为一个例子,考虑Foo.h头文件包含以下模板类。请注意, Foo<T>::f()方法是内联的,而Foo<T>::g()和Foo<T>::h()却不是。
// File "Foo.h"
template<typename T>
class Foo {
public:
void f();
void g();
void h();
};
template<typename T>
inline
void Foo<T>::f()
{
...
}
现在,假设文件Foo.cpp实际定义了非内联的Foo<T>::g()和Foo<T>::h():
// File "Foo.cpp"
#include <iostream>
#include "Foo.h"
template<typename T>
void Foo<T>::g()
{
std::cout << "Foo<T>::g()\n";
}
template<typename T>
void Foo<T>::h()
{
std::cout << "Foo<T>::h()\n";
}
假设文件main.cpp使用该模板创建一个Foo<int>并调用其方法:
// File "main.cpp"
#include "Foo.h"
int main()
{
Foo<int> x;
x.f();
x.g();
x.h();
...
}
如果你编译和(试图)链接这两个.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
]
由于模板友类的复杂性。下面是一个常见的例子:
#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_;
};
当然在某个地方我们会用到模板:
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);
在frend行中你也需要加入<>,如下所示:
#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_;
};
这些写法将有助于编译器更好地了解友元函数。值得一提的是,它会发现友元函数本身是模板。这消除了混乱。
另一种方法是在类中同时声明和定义该友元函数。例如:
#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 *'
以下是上面例子的源代码:
#include <map>
#include <algorithm>
#include <cmath>
const int values[] = { 1,2,3,4,5 };
const int NVALS = sizeof values / sizeof (int);
int main()
{
using namespace std;
typedef map<int, double> valmap;
valmap m;
for (int i = 0; i < NVALS; i++)
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
]
[最近创建的FAQ,感谢Victor Bazarov (in 9/06)。点击这里到最近修改过的下一个FAQ。]
你也许很吃惊,下面的代码是无效的C + +代码,即使如此通过有些编译器:
template<typename T>
class B {
public:
class Xyz { ... }; ← type nested in class
typedef int Pqr; ← type nested in class
};
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,因此
规则是这样的:当查找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
]
[最近修改,澄清(9 / 06)。点击这里到最近修改过的下一个FAQ。]
你也许很吃惊,下面的代码是无效的C + +代码,即使如此通过有些编译器:
template<typename T>
class B {
public:
void f() { } ← member of class
};
template<typename T>
class D : public B<T> {
public:
void g()
{
f(); ← bad (even though some compilers erroneously (temporarily?) accept it)
}
};
这可能会让你很伤脑筋,最好坐下来听我讲。
在函数D<T>::g()内,名字f不依赖于模板参数T,所以他们被称作为nondependent名字。另一方面B<T> 依赖模板参数T,因此
规则是这样的:当查找nondependent名字(比如f)的时候,编译器不会查找dependent基类(如B <T>中 )。
这并不意味着继承不起作用。类D <int>是仍然继承自类B <int>,编译器仍然让你可以隐式的做is- a转换(例如,D<int>*到 B <int> *),动态绑定仍然有效当虚函数被调用时,等等。但有一个如何查找名称的问题。
替代方案:
[ Top | Bottom | Previous section
| Next section | Search the FAQ
]
[最近修改,为了包括non-dependent名字,感谢 Victor Bazarov (in 9/06)。点击这里到最近修改过的下一个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
void f() { } ← member of class
};
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
]
E-mail the
author
[ C++ FAQ Lite | Table of contents
| Subject index | About the author
| ©
| Download your own copy ]
Revised
Jan 2, 2009