一、模板元编程库
在C++的标准库中提供了相关的元编程库,提供了一系列的对相关元编程相关的接口,极大的方便了开发者在元编程时的工作。在前面也分析过几个元编程的接口,今天接着分析一下指针处理的元编程接口std::is_pointer。
二、std::is_pointer
标准库中对其的定义为:
template< classT >
structis_pointer;
说明也写得非常清楚,它只能检查T是不是一个对象或函数的指针,还特别注明了包括void指针但不包括成员指针。它提供了对cv限定符的支持,其可能实现的源码如下:
template<classT>
structis_pointer :std::false_type {};
template<classT>
structis_pointer<T*> :std::true_type {};
template<classT>
structis_pointer<T* const> :std::true_type {};
template<classT>
structis_pointer<T* volatile> :std::true_type {};
template<classT>
structis_pointer<T* const volatile> :std::true_type {};
std::true_type这哥俩用到的地方还是挺频繁的。这段代码没啥可解析的,在不同的库中,实现有些大同小异,但基本都差不多。再看一个cppreference的例子:
#include<type_traits>
intmain()
{
structA
{
int m;
voidf(){}
};
int A::*mem_data_ptr = &A::m; // a pointer to member data
void (A::*mem_fun_ptr)() = &A::f; // a pointer to member function
static_assert(
! std::is_pointer<A>::value
&& ! std::is_pointer_v<A> // same thing as above, but in C++17!
&& ! std::is_pointer<A>() // same as above, using inherited operator bool
&& ! std::is_pointer<A>{} // ditto
&& ! std::is_pointer<A>()() // same as above, using inherited operator()
&& ! std::is_pointer<A>{}() // ditto
&& std::is_pointer_v<A*>
&& std::is_pointer_v<A const* volatile>
&& ! std::is_pointer_v<A&>
&& ! std::is_pointer_v<decltype(mem_data_ptr)>
&& ! std::is_pointer_v<decltype(mem_fun_ptr)>
&& std::is_pointer_v<void*>
&& ! std::is_pointer_v<int>
&& std::is_pointer_v<int*>
&& std::is_pointer_v<int**>
&& ! std::is_pointer_v<int[10]>
&& ! std::is_pointer_v<std::nullptr_t>
&& std::is_pointer_v<void (*)()>
);
}
是不是觉得代码很easy,确实如此。但越是这样越是要小心。
三、问题和分析
将上面的代码改一下,直接传递进去一个多级指针会是什么样呢?比如下面这样:
classDemo{};
intmain(){
static_assert(std::is_pointer<Demo *>::value, "Demo is not a pointer type");
static_assert(std::is_pointer<Demo **>::value, "Demo is not a pointer type");
return0;
}
这种情况顺利地通过了编译。表面上看上去没问题,如果只是单纯的处理指针可能觉得也没有什么问题啊。但实际上,如果实际的目的操作的是一个“*Demo”时,前者可以得到一个Demo的对象而后者只能得到一个Demo的指针,这就让后面的行为出现了异化。这对于普通编程来说还好发现,但对于模板编程时,可能就很难定位问题。这就需要进行预防性的处理。
有的开发者可能还没有细看说明,把成员内的指针提供给了这个接口应用,如下面这样:
structS {
intfoo(){ return0; }
};
intmain(){
static_assert(std::is_pointer<decltype(&S::foo)>::value, "S::foo is not a member pointer");
//static_assert(std::is_member_pointer<decltype(&S::foo)>::value, "S::foo is not a member pointer");//OK
return0;
}
这也是不正确的用法。说明中详细了表示了不支持成员指针的处理。所以在元编程的标准库中还提供了一个std::is_member_pointer,用来判断成员的指针属性的情况,也算是完善了std::is_pointer不能判断成员指针的情况。
四、处理方法
那么如何处理is_pointer只能检测最外层指针的情况呢?最简单的方法仍然是采用元编程库提供的std::remove_pointer递归判断。毕竟,几乎没有人写超过三级指针的情况,到二级已经是大多数人的极限了。用大牛的话说,不是说不可以用更多级的指针实现,但这恰恰说明了设计上的问题。扯远了,先看使用std::remove_pointer递归解决二级指针的处理方式:
#include<iostream>
#include<type_traits>
template<typename T>
voidcheckPointerLevel(T ptr){
ifconstexpr(std::is_pointer_v<T>){
std::cout << "this is pointer!";
// 递归检查指针级别
int level = 0;
using curType = T;
// remove_pointer处理并检查
using BaseType = std::remove_pointer_t<T>;
ifconstexpr(std::is_pointer_v<BaseType>){
std::cout << "twice-level pointer!" << std::endl;
} else {
std::cout << "first-level pointer!" << std::endl;
}
} else {
std::cout << "not a pointer!" << std::endl;
}
}
当然也可以使用SFINAE技术处理:
#include<iostream>
#include<type_traits>
#include<vector>
template<typename T>
voidimplCheck(T value, std::true_type){
using BaseType = typenamestd::remove_pointer<T>::type;
//业务处理
}
template<typename T>
voidimplCheck(T value, std::false_type){
//业务处理
}
template<classU>
voidprocess(U&& t) {
implCheck(std::forward<U>(t), std::is_pointer<std::remove_reference_t<U>>{});
}
其实原理都在前面分析过,此处不再赘述。代码也不复杂,明白几个基础的元编程接口即可。
五、总结
在应用库或者第三方接口时,要认真严格的查看相关的接口说明,一定不能想当然的进行利用。本文这个指针处理的元编程接口,就很容易让初学者上当。这就需要开发者能够全面细致的掌握应用的细节并在适当的例程中进行测试验证。这样才能更好的在工程实践中写出健壮稳定的代码。