C++ template tricks and techniques

 http://topic.csdn.net/u/20071013/19/c157fda3-1e2d-4f04-9550-f7d7906dd5d1.html

 

1,获得一个指针进行一次解引用之后的类型。
指针在解引用一次之后会得到它引用对象的类型。我们可以用偏特化来得到这个类型。
template <typename T>
struct rm_a_ptr
{
typedef T value_type;
};

template <typename T>
struct rm_a_ptr <T*>
{
typedef T value_type;
};

rm_a_ptr <int*>::value_type I = 5;

当然我们可以获得移出所有ptr,只需要把这个偏特化变通一下。这里就不用讨论了。

2,static_if
static_if就是在编译期的时候进行条件控制。其实#ifXXX这些编译预处理器就是做这些事情的,不过static_if还有一个额外的动作就是让语言的类型系统也参与运算。

template <bool Cond, typename TrueType, typename FalseType>
struct static_if
{
typedef TrueType value_type;
};

template <typename TrueType, typename FalseType>
struct static_if <false, TrueType, FalseType>
{
typedef FalseType value_type;
};

用模板偏特化判断第一个非类型模板参数进而决定选用TrueType和FalseType。

3,判断两个类型是否相同。
C++提供了对函数的重载,编译器在一系列的重载函数中来寻找最佳匹配。这是我们实现类型判断的基本依据。为此我们需要两个函数来完成这种匹配。
template <typename T> int match(T);
template <typename T> char match(…);
这两个函数都可以接纳所有类型的参数,但是第一个match可以让它只接纳指定的参数类型,来看看下面的代码。
match <int>(1);
通过 <int>来指定模板参数T为int,那么第一个match的参数就是int,而1正是int,所以这里的最佳匹配是第一个match而不是第二个。基于这一点,我们就可以完成两个类型是否相同的判断了。

template <typename T, typename U>
struct same_type
{
enum{ value = (sizeof(int) == sizeof(match <T**>((U**)0))) };
};

match <T**>((U**)0) 发生了什么?
我们把空指针0显式转换成U**然后拿给match与T**匹配。如果U和T是同一个类型,则match返回值的类型是int。 如果U和T不是同一个类型,则匹配第二个match,其返回值类型就是char。所以把sizeof(int)的值和sizeof match进行比较就可以把匹配的结果利用在编译期当中,然后再把这个值交由enum中的value来保存。

为什么不用match <T>(U())来进行匹配呢?因为如果U派生自T的话,那么匹配第一个match,尽管U和T的关系是is a,但是在这种情况下把他们当作成不是一样的更好。不然下面的例子你会犯愁
class A{};
class B: public A{};

bool x = same_type <A, B>::value;
if(x == same_type <B, A>::value) //永远都else.

那为什么要用二维指针而不直接是T*和U*的比较呢?原因其实和上面一样。

4,判断一个类型是不是函数类型。
数组有一个特点就是,元素的类型不能为函数类型和void。由此可以用以上的方法来实现这个判断
template <typename T> char match_func_type(T(*)[2]);
template <typename Function> int match_func_type(…);

在使用match_func_type的时候仍然要指定模板参数
match_func_type <void()>(0);
这会匹配第二个match_func_type,因为我们指定的模板参数是一个函数类型。由此可以看出,第一个match_func_type的参数会是一个指向函数类型数组的指针,显然这是不可能的,因为没有函数类型的数组。所以会匹配到第二个match_func_type。注意,我们还需要排除掉void类型。

template <typename F>
struct is_function_type
{
enum{value =  (same_type <F, void>::value == 0) && (sizeof(int) == sizeof(match_func_type <F>)(0))};
};
这里的表达式的伪码为
(F != void && match_func_type(…));

5,判断一个类型是不是函数指针
有了上面的功能,我们可以很快实现出这个。判断进行一次解引用之后的类型是不是函数类型就可以了。

template <typename F>
struct is_function_pointer
{
enum{value = is_function_type <typename rm_a_ptr <F>::value_type>::value};
};

6,我们得到了什么?
上面通篇的论述和指导展示了C++模板的一些作用。T不仅仅只是一个容器这么简单。但是这些在实际工作中看似不会有什么作用,或者只是让代码变得更炫的一些把戏,甚至成为证明自己比别人高明的手段。到最后,开始质疑这些代码是否有必要出现。

7,!@#$%^&*()_+
LoadLibrary + GetProcAddress + FreeLibrary是一件烦人的事情,每次用都要判断句柄,因此在一段时间里,处理这部分我都会Copy&Paste以前的代码,然后作点小小的修改。最后发现Copy&Paste也是一件烦人的事情,所以我写了一个类把这个工作给封装了。
class shared_wrapper
{
public:
shared_wrapper(const char* filename);
~shared_wrapper();
bool empty();
void* symbols(const char* symbol);
private:
HMODULE module_;
};

构造函数和析构函数分别负责LoadLibrary和FreeLibrary这是显而易见的。empty()返回true表示指定的动态库加载成功了。symbols则返回接口的地址。定义如下
void* shared_wrapper::symbols(const char* symbol)
{
if(empty())
throw  my_excep("shared_wrapper.symbols, empty shared library");

if(0 == symbol)
throw my_excep("shared_wrapper.symbols, null symbol");

void* result = ::GetProcAddress(module_, symbol);
if(0 == result)
{
std::string what = "shared_wrapper.symbols, no symbol named ";
what += symbol;
throw my_excep(what.c_str());
}

return result;
}

后来感觉symbols返回一个void*特不爽。干嘛不让它返回一个我们直接可以用的函数指针呢?于是我把它改成了这样
template <typename FuncPtr>
FuncPtr symbols(const char* filename)
{
//…
return (FuncPtr)result;
}

后来再一次编码过程中,顺手写了下面这行代码
Shared.symbols <void(int)>("interface")(5);
后来编译错误告诉我,我返回了一个函数。这时我顿悟, symbols应该接纳一个函数类型而不仅仅只接纳函数指针类型。因为最后symbols都应该返回的是一个函数指针,而指定函数类型已经提供了足够的类型信息了。所以我再次对symbols动了手脚。

现在symbols的模板参数可以是函数类型,也可以是函数指针类型。于是先写一个辅助类模板来计算该模板参数的函数指针类型方便后面使用。

template <typename T>
struct make_func_ptr
{
typedef typename rm_a_ptr <T>::value_type prototype;

typedef typename static_if <is_function_type <prototype>::value, prototype*, int>::value_type value_type;
};

在这里,我们可以把static_if理解成下面的伪码
prototype = typeof(*T);
if(prototype == function type)
return &prototype;
else
return int;

后面为什么会return int呢?只是借用一下,只要不return 函数指针类型就行了,下面会看到return int的作用。

Template <typename Function>
typename make_func_ptr <Function>::value_type
symbols(const char* filename)
{
typedef typename make_func_ptr <Function>::value_type fptr_type;
if(is_function_pointer <fptr_type>::value == 0)
throw my_excep("shared_wrapper.symbols, template <Function> is not a function type or a function pointer type");

//…
return (fptr_type)result;
}

上面return int的作用就在symbols里,如果return int则fptr_type就是int,那么下面的if就会捕捉到这个错误。这就是最后的模样了。

8,重新看待这些够炫的小把戏。
用代码来实现一些语言未曾提供的特性,难道这不是灵活和强大的表现吗?总是看到一部分人说这没用、那没用,也许是他们没有考虑过这些该怎么用。应用是否得当一切都来自于对问题的把握和理解。各位的观点是什么?

你可能感兴趣的:(C++ template tricks and techniques)