回首页 回首页 ◎ 设为首页  
◎ 收藏本站  
◎ 给我留言  
  
  首 页  C/C++教程  C++之父的FAQ  C/C++动向  C/C++源代码  C/C++误区  Unix/Linux  下载中心  乱七八糟  蚂蚁的Blog  
  当前位置:首 页 >> C++之父的FAQ >> C++ 风格与技术 FAQ >> [转] 为什么我无法限制模板的参数?
最 近 更 新
[转] 我该把const写在类..
[翻译] 我应该使用按值..
[翻译] 你如何命名变量..
[转] 何种代码布局风格..
[转] “int* p;”和“in..
[翻译] “cout”怎么念?
[转] 宏有什么不好吗?推荐
[翻译] static_cast 有..推荐
[翻译]为何C++里有些东..推荐
[翻译] i++ + i++ 的值..推荐
最 新 推 荐
[转] 宏有什么不好吗?推荐
[翻译] static_cast 有..推荐
[翻译]为何C++里有些东..推荐
[翻译] i++ + i++ 的值..推荐
[翻译] 数组有何不好之..推荐
[转] 为何delete操作不..推荐
[转] 有placement delet..推荐
[翻译] “new”和“mall..推荐
[转] C和C++风格的内存..推荐
[翻译] 为何标准容器效..推荐
热 门 排 行
[转] 我可以写“void ma..
[翻译] 什么是纯虚函数?
[转] 这个简单的程序…..推荐
[转] 我如何从标准输入..
[翻译] i++ + i++ 的值..推荐
[转] 我该把const写在类..
[转] 我怎样才能把整数..
[转] 我应该怎样处理内..推荐
[转] 宏有什么不好吗?推荐
[翻译] static_cast 有..推荐
站 内 搜 索

Web stdcpp.cn
关键词

搜索方式

搜索范围

精确匹配
广 告

[转] 为什么我无法限制模板的参数?


来源:C++ Style and Technique 作者:Bjarne Stroustrup 翻译:紫云英 等级:强烈推荐
发布于2007-06-08 17:50 被读424次 【字体:

Q: 为什么我无法限制模板的参数?

A: 呃,其实你是可以的。而且这种做法并不难,也不需要什么超出常规的技巧。

让我们来看这段代码:

	template<class Container>
	void draw_all(Container& c)
	{
		for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
	}
如果c不符合constraints,出现了类型错误,那么错误将发生在相当复杂的for_each解析之中。比如说,参数化的类型被要求实例化int型,那么我们无法为之调用Shape::draw()。而我们从编译器中得到的错误信息是含糊而令人迷惑的——因为它和标准库中复杂的for_each纠缠不清。

为了早点捕捉到这个错误,我们可以这样写代码:

	template<class Container>
	void draw_all(Container& c)
	{
		Shape* p = c.front(); // accept only containers of Shape*s

		for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
	}
我们注意到,前面加了一行Shape *p的定义(尽管就程序本身而言,p是无用的)。如果不可将c.front()赋给Shape *p,那么就大多数现代编译器而言,我们都可以得到一条含义清晰的出错信息。这样的技巧在所有语言中都很常见,而且对于所有“不同寻常的构造”都不得不如此。[译注:意指对于任何语言,当我们开始探及极限,那么不得不写一些高度技巧性的代码。]
 
不过这样做不是最好。如果要我来写实际代码,我也许会这样写:
	template<class Container>
	void draw_all(Container& c)
	{
		typedef typename Container::value_type T;
		Can_copy<T,Shape*>(); // accept containers of only Shape*s

		for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
	}
 
这就使代码通用且明显地体现出我的意图——我在使用断言[译注:即明确断言typename Container是draw_all()所接受的容器类型,而不是令人迷惑地定义了一个Shape *指针,也不知道会不会在后面哪里用到]。Can_copy()模板可被这样定义:
	template<class T1, class T2> struct Can_copy {
		static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
		Can_copy() { void(*p)(T1,T2) = constraints; }
	};
Can_copy在编译期间检查确认T1可被赋于T2。Can_copy<T,Shape*>检查确认T是一个Shape*类型,或者是一个指向Shape的公有继承类的指针,或者是用户自定义的可被转型为Shape *的类型。注意,这里Can_copy()的实现已经基本上是最优化的了:一行代码用来指明需要检查的constraints[译注:指第1行代码;constraints为T2],和要对其做这个检查的类型[译注:要作检查的类型为T1] ;一行代码用来精确列出所要检查是否满足的constraints(constraints()函数) [译注:第2行之所以要有2个子句并不是重复,而是有原因的。如果T1,T2均是用户自定义的类,那么T2 c = a; 检测能否复制构造;b = a; 检测能否赋值构造] ;一行代码用来提供执行这些检查的机会 [译注:指第3行。Can_copy是一个模板类;constraints是其成员函数,第2行只是定义,而未执行] 。
 
[译注:这里constraints实现的关键是依赖C++强大的类型系统,特别是类的多态机制。第2行代码中T2 c = a; b = a; 能够正常通过编译的条件是:T1实现了T2的接口。具体而言,可能是以下4种情况:(1) T1,T2 同类型 (2) 重载operator = (3) 提供了 cast operator (类型转换运算符)(4) 派生类对象赋给基类指针。说到这里,记起我曾在以前的一篇文章中说到,C++的genericity实作——template不支持constrained genericity,而Eiffel则从语法级别支持constrained genericity(即提供类似于template <typename T as Comparable> xxx 这样的语法——其中Comparable即为一个constraint)。曾有读者指出我这样说是错误的,认为C++ template也支持constrained genericity。现在这部分译文给出了通过使用一些技巧,将OOP和GP的方法结合,从而在C++中巧妙实现constrained genericity的方法。对于爱好C++的读者,这种技巧是值得细细品味的。不过也不要因为太执著于各种细枝末节的代码技巧而丧失了全局眼光。有时语言支持方面的欠缺可以在设计层面(而非代码层面)更优雅地弥补。另外,这能不能算“C++的template支持constrained genericity”,我保留意见。正如,用C通过一些技巧也可以OOP,但我们不说C语言支持OOP。]
 
请大家再注意,现在我们的定义具备了这些我们需要的特性:
  • 你可以不通过定义/拷贝变量就表达出constraints[译注:实则定义/拷贝变量的工作被封装在Can_copy模板中了] ,从而可以不必作任何“那个类型是这样被初始化”之类假设,也不用去管对象能否被拷贝、销毁(除非这正是constraints所在)。[译注:即——除非constraints正是“可拷贝”、“可销毁”。如果用易理解的伪码描述,就是template <typename T as Copy_Enabled> xxx,template <typename T as Destructible> xxx 。]
  • 如果使用现代编译器,constraints不会带来任何额外代码
  • 定义或者使用constraints均不需使用宏定义
  • 如果constraints没有被满足,编译器给出的错误消息是容易理解的。事实上,给出的错误消息包括了单词“constraints” (这样,编码者就能从中得到提示)、constraints的名称、具体的出错原因(比如“cannot initialize Shape* by double*”)

既然如此,我们干吗不干脆在C++语言本身中定义类似Can_copy()或者更优雅简洁的语法呢?The Design and Evolution of C++分析了此做法带来的困难。已经有许许多多设计理念浮出水面,只为了让含constraints的模板类易于撰写,同时还要让编译器在constraints不被满足时给出容易理解的出错消息。比方说,我在Can_copy中“使用函数指针”的设计就来自于Alex Stepanov和Jeremy Siek。我认为我的Can_copy()实作还不到可以标准化的程度——它需要更多实践的检验。另外,C++使用者会遭遇许多不同类型的constraints,目前看来还没有哪种形式的带constraints的模板获得压倒多数的支持。

已有不少关于constraints的“内置语言支持”方案被提议和实作。但其实要表述constraint根本不需要什么异乎寻常的东西:毕竟,当我们写一个模板时,我们拥有C++带给我们的强有力的表达能力。让代码来为我的话作证吧:

	template<class T, class B> struct Derived_from {
		static void constraints(T* p) { B* pb = p; }
		Derived_from() { void(*p)(T*) = constraints; }
	};

	template<class T1, class T2> struct Can_copy {
		static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
		Can_copy() { void(*p)(T1,T2) = constraints; }
	};

	template<class T1, class T2 = T1> struct Can_compare {
		static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }
		Can_compare() { void(*p)(T1,T2) = constraints; }
	};

	template<class T1, class T2, class T3 = T1> struct Can_multiply {
		static void constraints(T1 a, T2 b, T3 c) { c = a*b; }
		Can_multiply() { void(*p)(T1,T2,T3) = constraints; }
	};

	struct B { };
	struct D : B { };
	struct DD : D { };
	struct X { };

	int main()
	{
		Derived_from<D,B>();
		Derived_from<DD,B>();
		Derived_from<X,B>();
		Derived_from<int,B>();
		Derived_from<X,int>();

		Can_compare<int,float>();
		Can_compare<X,B>();
		Can_multiply<int,float>();
		Can_multiply<int,float,double>();
		Can_multiply<B,X>();
	
		Can_copy<D*,B*>();
		Can_copy<D,B*>();
		Can_copy<int,B*>();
	}

	// the classical "elements must derived from Mybase*" constraint:

	template<class T> class Container : Derived_from<T,Mybase> {
		// ...
	};
事实上Derived_from并不检查继承性,而是检查可转换性。不过Derive_from常常是一个更好的名字——有时给constraints起个好名字也是件需细细考量的活儿。

本文乃网上搜集得来,其版权归原作者和原出处所有。如有侵犯版权之处请与我联系,我将马上进行处理。



相关专题:C++ 之父的言论
[转] 我该把const写在类型前面还是后面?
[翻译] 我应该使用按值传递还是按引用传递?

上一篇:[转] 为何无法在派生类中重载?
下一篇:[翻译] 为何 vector<Apple*> 不能赋值给 vector<Fruit*>

共有评论 0 条 网友评分 1分 查看全部评论

查看全部评论

【发表评论】 评分:1分 2分 3分 4分 5分


验证码:

Powered By Www.Xydw.COM Ver1.14 管理
Copyright © 2005-2006 蚂蚁的 C/C++ 标准编程 All Right Reserved. XCMS
粤ICP备06014124号   站长:Antigloss