编译期成员函数检查

如何在编译期对某个类中是否有某个函数进行检查?假设有这样的使用场景。比如,对一系列的类进行序列化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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值