如何在编译期对某个类中是否有某个函数进行检查?假设有这样的使用场景。比如,对一系列的类进行序列化serialize,如果类中包含这个方法std::string serialize() ,就直接调用该方法,否则进行其他的处理。
伪代码如下:
template<typename T>
void serialize(const T& obj)
{
if (HasSerialize(obj))
obj.serialize();
else
...
}
首先解决识别的问题。设想有一个类,使用它的一个bool类型静态常量来表示是否有序列化方法,有true,没有false,伪代码如下:
template<typename T>
struct HasSerialize{
static const bool value = true/false;
};
如何能得到value的值呢?如何通过编译期求值?我们知道sizeof运算可以在编译期得到值,另外编译期是可以进行整数的运算的。如果有两个常量A和B,编译期是可以区别两个整数静态常量的。现在就是要想办法拿是否有序列化方法和A或B比较。C++模板基本的工具就是重载、trait和SFINE。假设两个模板函数,在匹配有序列化的情况下返回A,无法匹配的情况下返回B,这样就可以确定了。比如伪代码如下:
template<typename T>
A test(const T& obj);
template<typename T>
B test(const T& obj);
上面的代码,第一,在编译期使用静态常量时不能进行函数调用,只能使用编译期能决断的常数值;第二,使用静态函数,不能进行实例化对象调用。
下面来解决这两个问题:
第一个问题,一个类型,取sizeof运算可以得到常量值,因此可以定义两个不同的常量类型,解决。
typedef char yes;//A true
typedef char no[2]; // B false
第二个问题,加static就可以了。
如果一个类T包含某个方法,这个函数类型是这样的:
ReturnType (T::*)(Arguments);
对应的序列化方法是这样:
std::string (T::*)() const;
它的函数实例:
&T::serialize
放在一个辅助的结构中,如下:
template<typename T, T> reallyHas; // T是函数类型
test函数完整写一下就是:
template<typename T>
static yes& test(reallyHas<std::string (T::*)(), &T::serialize>*);
template<typename T>
static yes& test(reallyHas<std::string (T::*)() const, &T::serialize>*); // const版本
template<typename T>
static no& test(...)
常量value初始化
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
完整的代码如下:
template<typename T>
struct HasSerialize
{
typedef char yes;
typedef char no[2];
template<typename U, U> struct reallyHas;
template<typename C> static yes& test(reallyHas<std::string (C::*)(), &C::serialize>*){}
template<typename C> static yes& test(reallyHas<std::string (C::*)() const, &C::serialize>*){}
template<typename> static no& test(...){}
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
这样就有了一个测试是否包含序列化方法的结构了。
测试类如下:
class TestA{
public:
TestA(){}
std::string serialize() const { return std::string("TestA::serialize"); }
};
class TestB{
public:
TestB(){}
};
再考虑文章开始的问题,如果直接使用HasSerialize
template<typename T>
void serialize(const T& obj)
{
if (HasSerialize<T>::value)
std::cout << "yes " << obj.serialize() << std::endl;
else
std::cout << "no" << std::endl;
}
serialize(TestA());
serialize(TestB());
发现编译报错:’serialize’ : is not a member of ‘TestB’,是在使用TestB的情况下对if分支也进行了编译处理。
再次考虑使用重载的方法:
template<bool v, typename T>
void serialize(const T& obj)
{
}
template<true, typename T> // 写不了了,函数没有偏特化
void serialize(const T& obj)
{
obj.serialize();
}
考虑使用类方法吧
template<bool v, typename T>
struct DummySerialize
{
static void serialize(const T& obj) {}
};
template<typename T>
struct DummySerialize<true, T>
{
static void serialize(const T& obj) { obj.serialize();}
};
测试:
DummySerialize<HasSerialize<TestA>::value, TestA>::serialize(TestA());
DummySerialize<HasSerialize<TestB>::value, TestB>::serialize(TestB());
其实还有一个更简单的办法,使用enbaleif,下一篇再详细解析它。
参考:
Is it possible to write a template to check for a function’s existence?
An introduction to C++’s SFINAE concept: compile-time introspection of a class member