在C++中侦测内嵌类型的存在(rev#2)
By 刘未鹏(pongba)
C++的罗浮宫(http://blog.youkuaiyun.com/pongba)
动机(Motivation)
假设一所大学的注册系统提供了一个注册函数:
template<class T>
void Register(T person)
{
Register(person, typename T::person_tag());
};
而对于注册者有以下几种标识:
struct student_tag{};
struct teacher_tag{};
还有Register的几个供内部使用的重载版本:
template<class T> void Register(T p, student_tag){...} // 注册学生
template<class T> void Register(T p, teacher_tag){...} // 注册教师
并规定学生类一定要在内部typedef student_tag person_tag,教师类typedef teacher_tag person_tag,这样,当传给起初的那个Register的对象为学生类对象时,typename T::person_tag()其实就构造了一个student_tag对象,从而激发函数重载,调用Register内部版本的template<class T> void Register(T p, student_tag)版本。其他情况亦均有对应。这是泛型编程里的常用手法(静态多态),STL里屡见不鲜。
问题是,现在学校里假如不止学生教师,还有工人,警卫等其它人员。如果他们不会在类内部typedef任何东西,则Register需要一种机制以确定T内部是否typedef了某个标识符(例如person_tag)。如果没有,就默认处理。如果有,则再进行更详细的分类。
实现(Implementation)
这个问题可能有两个实现途径。
一是利用函数重载,具体如下:
typedef char (&yes_type)[1]; // sizeof(yes_type)==1
typedef char (&no_type)[2]; // sizeof(no_type)==2
以上的两个typedef用于识别不同的重载函数。char (&)[1]表示对char[1]数组的引用,所以sizeof(char(&)[1])==sizeof(char[1])==1。注意围绕&符号的一对圆括号,它们是必要的,如果没有将会导致编译错误,正如char* [1]将被解析为char*的数组,char& [1]将被解析为引用的数组,而后者是非法的。将&用圆括号包围则改变了运算符的结合优先序,这将被解析为对char[1]数组的引用。
template<class T>
struct does_sometypedef_exists
{
template<class U>
static yes_type check(U, typename U::key_type* =0); // #1
static no_type check(...);
static T t; // 声明
static const bool value = sizeof(check(t))==sizeof(yes_type);
};
注意,#1处,*和=之间的空格是必要的,否则编译器会将它解析为operator*=操作符。
在我的VC7.0环境下,以下测试是成功的:
struct A{};
struct B
{
typedef int key_type;
};
int main()
{
std::cout << does_sometypedef_exists<A>::value<<' ' // 0