Andrew's Blog

Make things as simple as possible, but no simpler -- Albert Einstein

导航

如何在编译时刻判断两个类型是否可以自动转换?
——《Modern C++ Design》读书笔记(1)

设T和U是任意的两个类型,用什么方法判定类型T可以自动转换到类型U呢?

答案是:借助sizeof运算符。

其实,sizeof的实力相当强大。不论是什么类型,也不论是多么复杂的
表达式,只要把它交给sizeof,结果都将返回该表达式的值的类型长度。
也就是说,sizeof的背后必须暗藏一整套推导机制,它可以推导出表达式
值的类型。最终,sizeof并不关心整个表达式,而只是返回结果的长度。

我们的基本思想是,使用sizeof和函数重载机制。

声明两个重载函数,一个函数的形参接受可以转换为U的类型;另一个函数的
形参接受任何其他类型。使用类型T的临时变量来调用重载函数。如果接受U的函数
被调用,则可断定T是能够转换为U的。如果接受任何其他类型作参数的函数被调用
,那么T不能转换为U。

为了判断到底哪一个函数被调用,我们要让两个函数的返回类型具有不同的大小,
然后用sizeof来检查它们。只要能区分出来大小,类型本身是什么倒无关紧要。

首先,建立两个大小不同的类型:

typedef char Small;
class Big { char dummy[2]; };

根据C++标准规定sizeof(char)等于1,于是sizeof(Small)等于1。Big的大小我们
不知道,但是它肯定比1要大。

然后,声明一个函数,它接受U作为参数类型,并返回Small:

Small Test(U);

再声明一个重载函数版本,它必须在不能进行自动类型转换的情况下才被系统调用,
这就需要使用到省略号参数了:

Big Test(...);

下面对Test函数调用应用sizeof运算符:

bool const convExists = sizeof(Test(T())) == sizeof(Small);

对了,就是它!这里T()表示调用T的缺省构造函数建立一个临时变量,sizeof会从
这个表达式中抽取出它的值的长度。长度只能是两个值:sizeof(Small)或sizeof(Big)
中的一个。这取决于那个版本的重载函数会被调用,而事实是只有在T可以自动转换
为U的情况下Small Test(U)函数才会调用,从而convExists==true。

有一个问题:如果T不存在缺省构造函数,那么上述表达式在求值过程中岂不会出现
编译错误?该如何解决呢?可以巧妙的使用如下函数:

T MakeT();  // 没有实现定义
bool const convExists = sizeof(Test(MakeT())) == sizeof(Small);

为什么这种方案可行呢?关键在于sizeof的神奇特性:因为sizeof在推导类型长度的
过程中,并不实际求取任何表达式的值,没有表达式会被真正地计算出来。

好了,一切就绪。现在我们可以将上述方法用一个类模版封装起来,它将封装所有
的类型推导,而仅提供结果数据:

template<class T, class U>
class Conversion
{
    type char Small;
    class Big { char dummy[2]; };
    static Small Test(U);
    static Big Test(...);
    static T MakeT();
public:
    enum { exists =
           sizeof(Test(MakeT())) == sizeof(Small) };
};

下面编写主函数来测试Conversion类:

int main()
{
    using namespace std;
    cout << Conversion<double, int>::exists << ' '
         << Conversion<char, char*>::exists << ' '
         << Conversion<size_t, vector<int> >::exists << endl;
}

该程序输出结果:

1 0 0

说明:虽然std::vector存在一个接受size_t作为参数的构造函数,但是由于
该构造函数被声明为了explicit,因此不能从size_t自动转换为std::vector.

另外,我们还可以在Conversion中实现一个常量sameType,以判断T和U是否
具有相同的类型:

template<class T, class U>  // 主模板
class Conversion
{
    type char Small;
    class Big { char dummy[2]; };
    static Small Test(U);
    static Big Test(...);
    static T MakeT();
public:
    enum { exists =
           sizeof(Test(MakeT())) == sizeof(Small) };
    enum { sameType = false };
};

template<class T>   // 部分特化模板
class Conversion<T, T>
{
public:
    enum { exists = true, sameType = true };
}

当T和U的类型一样时,部分特化模板会被优先实例化,因此exists和sameType都为true。

借助于Conversion模板类,我们很容易判断T和U是否存在继承关系,具体如何进行,还是
等到下次再说吧……