[思考]如何给参数确定的函数模板,加上对类型具体要求的显式声明?
首先考虑下面一个函数模板,他可以打印stl容器到cout。
template< class T >
void out( const T& coll ){
for( typename T::const_iterator pos = coll.begin(); pos != coll.end(); ++pos )
std::cout<< *pos << ' ';
}
用法:
list<int> coll( 10, 1 );
out( coll ); // 输出10个1
现在他对T的要求是隐式的。如果 T不包含 T::const_iterator 镶嵌类型,那么实例化该模板就会失败。 但是在函数重载的情况下,隐式声明是不够的。 比如有下面的重载版本 out,他用于输出指针的地址:
template< class T >
void out( const T* p ){
std::cout<<p;
}
现在假如有 int* pi,而且只有这个版本的out,那么 out( pi ) 就会输出pi的地址。 然而一旦两个out版本都出现,两个都可以匹配,而且都不是完美匹配:
const T& ------- int* -> const int* &
const T* ------- int* -> const int*
C++重载规则认为,前者是更好的匹配!所以他会试图实例化那个用于stl容器的版本。那个版本显然不能成功,他要求T::const_iterator,int*当然不支持这个功能。 换句话说,在函数重载的情况下,我们可能需要显式声明 T 的某些特性,比如T拥有一个镶嵌类型 const_iterator。
///////////////////////////////////////////////////
如果是类模板,我们可以这样做:
template< class T, class CONSTRANT = T::const_iterator >
而不提供 CONSTRANT 参数,这样强迫 T 拥有const_iterator 成员。但是函数模板不允许缺省模板参数。
如果函数允许更多参数,我们用缺省参数强迫T类型,可以这样:
template< class T >
void out( const T& coll, T::const_iterator* = 0 ){ // 一个匿名的指针,显式声明T的限制
for( typename T::const_iterator pos = coll.begin(); pos != coll.end(); ++pos )
std::cout<< *pos << ' ';
}
上面的例子可以正确强迫T 提供 const_iterator 。 但是在某些环境下,我们没有提供多余参数的权力,比如 operator<< 中。 这种情况如何是好?
////////////////////////////////////////////////
考虑下面的用于容器的版本声明,他的意图是打印容器内容:
template< class Elem, class Trait, class T >
std::basic_ostream<Elem, Trait> operator<<( std::basic_ostream<Elem, Trait>&, const T& );
但是,他会令标准库的下面输出指针的版本对 non_const 指针失效:
template< class Elem, class Trait, class T >
std::basic_ostream<Elem, Trait> operator<<( std::basic_ostream<Elem, Trait>&, const T* );
现在的问题是:前者既是函数模板(不能提供模板缺省参数),又是运算符 operator<<重载(不能提供额外的函数参数) 如何令前者显式要求 typename T::const_iterator 必须存在?
////////////////////////////////////////////////
附测试代码:
template < class Elem, class Trait, class T >
std::basic_ostream<Elem, Trait>& operator<<( std::basic_ostream<Elem, Trait>& os, const T& coll ){
for( typename T::const_iterator pos = coll.begin(); pos != coll.end(); ++pos )
os<< *pos << ' ';
return os;
}
int main(){
std::list<int> coll( 10, 1 );
std::cout<< coll <<std::endl; //ok
int* pi = 0;
//std::cout<< pi <<std::endl; //!! failed
}
问题点数:100、回复次数:35Top
1 楼Jinhao(辣子鸡丁·GAME就这样OVER了)回复于 2006-03-25 10:53:44 得分 50
template<typename _OStream, typename T >
void out(_OStream& os, const T& coll, typename T::const_iterator * = 0 ){
for( typename T::const_iterator pos = coll.begin(); pos != coll.end(); ++pos )
os<< *pos << ' ';
}
template<typename _OStream, typename T >
void out(_OStream& os, const T* p ){
printf("%p", p);
}
template < typename Elem, typename Trait, typename T >
std::basic_ostream<Elem, Trait>& operator<<( std::basic_ostream<Elem, Trait>& os, const T& coll )
{
out(os, coll);
return os;
}
不过对于提供 template<typename T>std::ostream& operator<<(std::ostream& , T); 这样的东西的确很不好...为什么要去打破标准库的规则呢??Top
2 楼xlsue(小林)回复于 2006-03-25 11:39:03 得分 0
很久不用模板了,关注一下。。。Top
3 楼guyanhun(老婆说的都是对的!努力做个好老公!)回复于 2006-03-25 11:50:53 得分 0
study .
没看懂啥意思 ~~Top
4 楼corrupt(喜欢 睡在床板下 的思考)回复于 2006-03-25 12:08:04 得分 0
用特例???
Top
5 楼milee(业余程序员)回复于 2006-03-25 13:35:45 得分 0
好东西
日后再学Top
6 楼ox_thedarkness()回复于 2006-03-25 14:02:04 得分 0
哈哈,Jinhao(辣子鸡丁·十八中门口打望) ( ) 思路很有趣,“既然覆盖了标准的版本const T* ,那么我就亲自接管被覆盖的部分,实作一份一样的”,但是如你所言,这样仍然破了标准库的规则。
鸡丁的做法想当于一个自定义扩展的库 —— 而且不容于其他库,所以耦合度非常高。
当用户需要加入其他匹配度相同或者更低的重载时,不能直接重载operator<<, 而是“注册”有显式要求的out 版本,经由唯一的 const T& 版本充当中介。
有没有其他技巧,令 operator<< 这样的二元运算符重载能直接加上对模板参数的显式要求? 若是可以直接显式要求,那么就不存在对标准库的规则的破坏了。Top
7 楼ox_thedarkness()回复于 2006-03-25 14:18:36 得分 0
假如 C++ templates 第13章提到的未来方向可能提供的特性已经实现,那么我们可以简单的:
1 假如下一版C++ 标准支持缺省函数模板参数:
template < class Elem, class Trait, class T, class M = typename T::const_iterator >
利用缺省参数M 强制 T::const_iterator 存在
2 假如支持typeof :
template < class Elem, class Trait, class T>
... operator<<( ... , typeof(T::const_iterator, const T& ) rhs )
利用逗号表达式,强制T::const_iterator 存在
但是现阶段有什么办法呢?
Top
8 楼shenmea00000(学习中~~~)回复于 2006-03-25 15:35:28 得分 0
看不出什么门道~~~~~Top
9 楼Jinhao(辣子鸡丁·GAME就这样OVER了)回复于 2006-03-25 15:38:11 得分 0
重载不允许你去接管...要提供一个泛型的 operator<<(std::ostream&, const T&)几乎是不可能,因为现在不能让所有的T都具备operator<<(std::ostream&, const T&)对T的concept要求。
目前的方法只有依赖于提供一层helper这样的东西。而让 operator<<( std::basic_ostream<Elem, Trait>&, const T&) 进行了泛化[注意不是 operator<<(std::ostream&, const T&)],将导致一个很隐蔽的错误。看看上面的第2个out,我用的是 printf("%p", p);而非 os<<p; 这就是那个错误。即使下一个具有更好的类型系统的C++版本的出现,也未必能解得LZ的问题... 因为LZ的问题相当于用operator new 去实现 operator newTop
10 楼howyougen(夫孝,德之本也,教之所由生也)回复于 2006-03-25 21:32:18 得分 0
为什么要对容器操作,而不是对序列操作呢?Top
11 楼ox_thedarkness()回复于 2006-03-25 22:33:50 得分 0
- - 算了,偶给他加个强一点的制约,要求T是一个双模板参数的模板,暂时堵住 T* 问题。
而且目前这东西放在偶的一个子名字空间内,暂时不会有冲突。 比起安全,偶还是比较喜欢易用性。
template < class Elem, class Trait,
class E, class Alloc,
template<typename, typename> class T>
std::basic_ostream<Elem, Trait>& operator<<(
std::basic_ostream<Elem, Trait>& os,
const T<E, Alloc>& coll )
{
for( typename T<E, Alloc>::const_iterator pos = coll.begin(); pos != coll.end(); ++pos )
os<< *pos << ' ';
return os;
}
在偶发现新约束技巧之前,看来这个目前已知唯一完美的做法是你上次那样,对每个容器提供一个单独的模板,不过个人感觉麻烦而且不优雅,要是能给 T 加一些更加实质的显式限制就好了。
恩阿... 暂时放下... 揭帖....Top
12 楼ox_thedarkness()回复于 2006-03-25 22:44:24 得分 0
- - 算了,明天揭帖... 说不定还有一线希望涅...?
再次重申问题是:如何给模板参数 T 更加强的限制? 上面的代码中的限制是: T 是一个双参数模板。 没有办法进一步说明 T有内嵌定义 T::const_iterator ?Top
13 楼ox_thedarkness()回复于 2006-03-25 22:56:57 得分 0
to howyougen(谦受益) ( )
对序列操作的话有些麻烦,因为你必须同时制定 begin 和 end, 而 cout<< 只接受一个右端参数。 如果用仿函数的话,用起来类似这样:
cout<< show_iter( coll.begin(), coll.end() ) <<endl;
他和
copy( coll.begin(), coll.end(), ostream_iterator<int>( cout, " " ) );
相比短不了多少。而如果像下面那样多方便!
cout<< coll <<endl;
所以偶目前还没有着手写序列输出。Top
14 楼aflyinghorse()回复于 2006-03-26 00:17:09 得分 0
如何给模板参数 T 更加强的限制?
好像目前没有语言层面的支持吧。
boost倒是有很多用于概念检查的代码片断,但他的目的是要更早的对模板参数检查,让编译器
尽可能早的报告错误。Top
15 楼shantai(阿山)回复于 2006-03-26 06:06:15 得分 0
模板用的不是很多
关注中Top
16 楼pgmsoul(游侠)回复于 2006-03-26 08:12:00 得分 0
模板函数不支持默认参数,原因何在?Top
17 楼strangerryf(白痴与白痴讨论的结果一定是比白痴更为白痴的结论)回复于 2006-03-26 09:29:20 得分 0
模板不支持默认参数这个是在模板函数才有的限制,在类模板可以支持。
小ox真是超级模板粉丝啊,每天晚上都跟那个潜水的同学讨论这个。Top
18 楼xiao2004()回复于 2006-03-26 09:45:09 得分 0
我们并不总是能够写出对所有可能被实例化的类型都是最合适的函数模板 在某些情况
下 我们可能想利用类型的某些特性 来编写一些比模板实例化的函数更高效的函数 在有
些时候 一般性的模板定义对于某种类型来说并不适用 例如 假设我们有函数模板max()
的定义
// 通用的模板定义
template <class T>
T max( T t1, T t2 ) {
return (t1 > t2 ? t1 : t2);
}
如果函数模板用const char*型的模板实参实例化 并且我们还想让每个实参都被解释为
C风格的字符串 而不是字符的指针 则通用模板定义给出正确的语义就不正确了 为了获
得正确的语义 我们必须为函数模板实例化提供特化的定义
在模板显式特化定义 explicit specialization definition 中 先是关键字template和一对
尖括号 <> 一个小于号和一个大于号 然后是函数模板特化的定义 该定义指出了模板
名 被用来特化模板的模板实参 以及函数参数表和函数体 在下面的例子中 为max(const
char*, const char*)定义了一个显式特化
#include <cstring>
// const char* 显式特化:
// 覆盖了来自通用模板定义的实例
typedef const char *PCC;
template<> PCC max< PCC >( PCC s1, PCC s2 ) {
return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );
}
由于有了这个显式特化 当在程序中调用函数max(const char*,const char*)时 模板不会
用类型const char*来实例化 对所有用两个const char*型实参进行调用的max() 都会调用这
个特化的定义 而对于其他的调用 根据通用模板定义实例化一个实例 然后再调用它
不晓得楼主是不是要的这些类似的东东?
c++ primer书上Top
19 楼Acoolice()回复于 2006-03-26 10:30:05 得分 0
学习Top
20 楼ox_thedarkness()回复于 2006-03-26 11:02:39 得分 0
谢谢楼上的,不过可惜不是的。 现在的问题不是重载或者特化不够细致,而是两个不同的重载冲突,引发二义性或者一个覆盖了另一个。
今天遇到更加麻烦的问题... 模版重载真叫人头大... 下面这数组版本的 << 为什么在VC下能用,在gcc下不能用啊?
包含下面代码,VC下假如有数组 int str[any], 用 cout<< str 即可输出其内容;而gcc下第一个模板函数则被默认的比下来了?只是和定义之前一样输出首地址而已。
// operator<< on arrays
// There is a pitfall in this implementation
// It conflicts with standard lib on const char* version
// WARNING: this function would cover the default const char* version
// that would cause preformance decline
template < class T, size_t sz >
std::ostream& operator<<( std::ostream& os, const T(&ar)[sz] ){
for( size_t i = 0; i < sz; ++ i ) os<< ar[i] << ' ';
return os;
}
// This template avoid ambiguous on const char* in the occasion of ostream output
// It's a full matching overload. This is to cover the pitfall of the array out.
std::ostream& operator<<( std::ostream& os, const char* ar ){
if( !ar ) return os<< (void*) ar;
while( *ar ) os<<*ar++;
return os;
}
Top
21 楼aflyinghorse()回复于 2006-03-26 14:50:21 得分 0
我再 vc7 和 dev-cpp下的结果是一样的 : 1 2 3
下面是这个程序:
#include <iostream>
using namespace std;
// operator<< on arrays
// There is a pitfall in this implementation
// It conflicts with standard lib on const char* version
// WARNING: this function would cover the default const char* version
// that would cause preformance decline
template < class T, size_t sz >
std::ostream& operator<<( std::ostream& os, const T(&ar)[sz] ){
for( size_t i = 0; i < sz; ++ i ) os<< ar[i] << ' ';
return os;
}
// This template avoid ambiguous on const char* in the occasion of ostream output
// It's a full matching overload. This is to cover the pitfall of the array out.
std::ostream& operator<<( std::ostream& os, const char* ar ){
if( !ar ) return os<< (void*) ar;
while( *ar ) os<<*ar++;
return os;
}
int main()
{
int a[3]={1,2,3};
cout << a;
cin.get();
}
Top
22 楼aflyinghorse()回复于 2006-03-26 14:51:53 得分 0
LZ试试新版本的 dev-cpp?Top
23 楼pongba(刘未鹏|http://blog.csdn.net/pongba)回复于 2006-03-26 23:46:31 得分 50
to ox_thedarkness():
很简单,既然不能在参数上和模板缺省参数上做文章,那么在返回类型上用enable_if不就行了?
例如:
template<typename T>
typename boost::enable_if<has_const_iterator<T> >::type
f(...){...}
另外,const T&跟const T*的重载决议并不像你想的那样是前者优于后者,实际上,不管p是int*还是int const*,当前标准下的结果都应该是后者优于前者。对于int*,是exact match跟qualification conversion的比较,高下立判。然后对于int const*,重载决议不能决出结果,partial order rules这时就会接管任务,根据目前的rule,仍然是后者更特殊化一些。不过由于这一部分的规则十分晦涩,鲜有编译器能够完全conformance的实现,所以才会一个编译器一个样。目前partial order的规则还在修订中,所以建议还是不要依赖这一黑暗角落。
--
C++的罗浮宫
http://blog.csdn.net/pongbaTop
24 楼ox_thedarkness()回复于 2006-03-27 11:06:19 得分 0
- -b 昨天一整天rp了,看不见帖子正文,现在好了...
to aflyinghorse() ( ) 的确是版本,偶原来 devcpp 4.9.8.0 ;装了 devcpp 4.9.9.2 之后成功了。
boost::enable_if ? 谢pongba(刘未鹏) ,没怎么用过boost,偶下午仔细研究下... boost还真强啊。
现在这里的规则真是黑暗... 昨天又遇到一个重载决议VC7 与 devcpp 不同的情况。 感觉目前 C++ 模版的确强大,但是表达能力并不够强,规则也并不完善,尤其是决议部分相当复杂和诡异。Top
25 楼ox_thedarkness()回复于 2006-03-27 12:26:17 得分 0
- - 厄,boost::has_const_iterator 在哪个头文件里面?
boost\type_traits.hpp 里面似乎没有的说Top
26 楼pongba(刘未鹏|http://blog.csdn.net/pongba)回复于 2006-03-27 13:43:01 得分 0
has_const_iterator不是boost里的,你不会自己写么?呵呵。Top
27 楼aflyinghorse()回复于 2006-03-27 15:51:51 得分 0
has_const_iterator 怎么写?
利用模办类的特化?Top
28 楼sandrowjw(我的小猫照片给弄坏了,心都碎了)回复于 2006-03-27 16:38:45 得分 0
mark
以前见过boost里判断两个类是否继承关系的trait,感觉判断const_iterator应该比那个简单,总之就是类似那个返回类型长度不同的函数重载(Loki里好像也有)。Top
29 楼ox_thedarkness()回复于 2006-03-27 16:46:54 得分 0
= = 确实不会... 瞎写了一下还是不行,下面的写法在 VC7.1 和 DevCPP 4.9.9.2 都通不过过:
template <typename T>
struct has_iter{
typedef void void_type;
typedef T type;
typedef typename T::const_iterator type_must_exsit;
};
template <typename T>
typename has_iter<T>::void_type f( const T& coll ){
copy( coll.begin(), coll.end(), ostream_iterator< T::value_type >( out, " " ) );
cout<<endl;
}
template <typename T>
void f( const T* n ){
cout<< n <<endl;
}
int main(){
list<int> coll(10, 1);
int* i = 0;
f( coll );
f( i );
}Top
30 楼pongba(刘未鹏|http://blog.csdn.net/pongba)回复于 2006-03-27 18:31:06 得分 0
http://blog.csdn.net/pongba/archive/2004/08/24/82783.aspxTop
31 楼Jinhao(辣子鸡丁·GAME就这样OVER了)回复于 2006-03-27 23:06:51 得分 0
template<typename _T>
class has_const_iterator
{
template<typename _U>
static int _m_dispatcher( typename _U::const_iterator * );
template<typename _U>
static char _m_dispatcher(...);
public:
static const bool value = sizeof(_m_dispatcher<_T>(0)) - sizeof(char);
};Top
32 楼aflyinghorse()回复于 2006-03-27 23:29:57 得分 0
简单测试一下楼上的程序:
#include <iostream>
#include <vector>
using namespace std;
template<typename _T>
class has_const_iterator
{
template<typename _U>
static int _m_dispatcher( typename _U::const_iterator * );
template<typename _U>
static char _m_dispatcher(...);
public:
static const bool value = sizeof(_m_dispatcher<_T>(0)) - sizeof(char);
};
int main()
{
cout << "int has_const_iterator: " << has_const_iterator<int>::value << endl;
cout << "vector has_const_iterator: " << has_const_iterator<vector<int> >::value << endl;
cin.get();
}
结果是:
int has_const_iterator: 0
vector has_const_iterator: 1
Top
33 楼ox_thedarkness()回复于 2006-03-28 00:29:34 得分 0
喔阿... 真神阿... 谢Jinhao(辣子鸡丁·十八中门口打望) ( ) 的, ok 了~~
pongba(刘未鹏) ( ) 的文章研究了半天... 刚才看了鸡丁的才发现原来偶 == 和 != 用反了... orz... 怪不得不行,那壶不开提哪壶.... 真失败阿... 而且诡异的是,一个方法OK其他方法也莫名其妙OK... - -b
然后发现VC 成功了但是 DevCPP 失败了... 修改中发现VC下又不行了... 苦海研究中... 不过应该差不多了。
恩~~ 那么... 最后一个问题... 能否令 basic_stream 对应的 operator<< 重载同类参数呢?
比如, 书写两个对应(或者间接对应) const T& 的重载版本, 但是第一个要求 has_const_iterator<T> ,第二个则要求 is_a_pair<T>
即,令自定义的 operator<< 重载只要特性声明不通过,不会阻止其他相似重载?Top
34 楼ox_thedarkness()回复于 2006-03-28 01:33:27 得分 0
恩搞定了~~ 现在偶的数组输出模板不会覆盖标准的 char* 版, stl 模板也更加准确了~~
赞~~ 只剩下如何不影响其他重载的问题 —— 返回值不属于重载要求部分; 如何令 “实际参数对应版本相同” 的模板不相互冲突呢?Top
35 楼ox_thedarkness()回复于 2006-03-28 01:56:05 得分 0
哦~~ 测试了一下发现居然可以重载? 看来以前对模版重载的看法是错误的呢~~
好~~ 揭帖~~Top




