traits编程技法1——型别推导、偏特化与traits

我们在使用迭代器时经常会使用到其相应型别(associated type),例如某个场景需要声明一个变量,他的型别为“迭代器所指对象的型别”,即value type。但是C++并没有获取类型的机制,即使是typeid()也只能获得型别名称而不能用它来声明变量。

例如我们要写一个泛型swap函数,它接受一对迭代器itera和iterb,我们要做的是将两个迭代器指向的值进行交换。可能大多数人第一反应是这么做:

Type t = *itera;
*itera = *iterb;
*iterb = t;
问题的关键在于: 我们不知道这个Type是什么。可能会有人想到利用std::swap函数可以解决型别未知的问题:

std::swap(*itera, *iterb);


1.参数推导

先看std::swap的实现:
template <class T>
void swap(T& a, T& b)
{
    T t(a);
    a = b;
    b = t;
}

std::swap的原理正是函数模板的参数推导(argument deduction)机制。

swap函数是一个模板函数,被调用后编译器会自动进行参数推导,导出型别T,解决了问题。这种通过编译器自动推导型别的方式在STL与boost库中大量存在。

但是通过这种方法只能推导出“迭代器所指对象的型别”,而对其他的迭代器相应型别推导就可能无能为力了。例如我们需要将value type用于函数的返回值,而参数推导机制只能作用于函数的参数,无法推导返回值的型别。因此我们需要更加普适的解决方案。


2.内嵌型别

我们可以声明一个内嵌型别来解决这个问题,例如:
template <class T>
struct Iter
{
    typedef T value_type;   // 内嵌型别声明
    T* ptr;
    Iter(T* p = NULL) : ptr(p) {}
    T& operator*() const { return *ptr; }
    //...
};

template <class I>
typename I::value_type  //func返回值类型
func(I iter)
{
    return *iter;
}

int main()
{
    Iter<int> iter(new int(3));
    cout << func(iter);
    return 0;
}
注意func()的返回值必须加上typename关键字,因为T是一个template参数,在它被编译器具现化之前无法获取其类型。即编译器并不知道Iter<T>::value_type代表的是什么。关键词typename告诉编译器这是一个型别才能成功编译。

这种方法看起来很完美了,但是并非所有的迭代器都是一个class,原生指针也是迭代器的一种。原生指针显然是无法定义内嵌型别的,但STL必须能够接受原生指针这种迭代器,那我们就要考虑如何将一般方法针对某些特殊情况,例如针对原生指针,来特殊化处理呢?

3.偏特化

C++ 偏特化(partial specialization)机制能够做到所谓“一般情况的特殊化处理”。下面就对偏特化进行简要说明。

偏特化的大致含义是:如果一个模板类拥有一个以上template参数,我们可以针对其中某个(或某些,单非全部)参数进行特化。即在泛化设计中提供一种特化版本来适应某些特殊情况。
例如一个模板类Demo,它有一个模板参数T:
//这个泛化版本接受T为任何型别
template <class T>
class demo
{
    //...
};
我们将他设计成一个偏特化版本,这个版本只能接受T为原生指针的情况:
//这个特化版本仅适用于T 为原生指针的情况
template <class T>
class demo<T*>
{
    //...
};
上述偏特化的例子中,T 为原生指针便是一个对T为任何型别的特殊化。

通过偏特化机制,我们就能将上一小节中无法为原生指针定义“内嵌型别”的问题。原生指针不是class,无法定义内嵌型别,但是我们可以针对“迭代器的模板参数是原生指针”这种情况来设计特化版的迭代器。

4.traits编程技法

Traits字面意义就是萃取。下面就设计一个模板类,用来专门“萃取”迭代器的特性(value type就是迭代器特性之一):
template <class I>
struct iterator_traits
{
    typedef typename I::value_type value_type;
    //如果 I 有自己定义的value type,则通过traits萃取出来的value_type就是I::value_type
};
这种写法的意义是:如果I定义了自己的value type,通过这句话得到的的value_type就是I::value_type。因此之前的func()函数可以改为如下形式:
//func可以改为
template <class I>
typename iterator_traits<I>::value_type //func返回值型别
_func(I iter)
{
    return *iter;
}
看似这种写法只是多了一个间接层,并没有比第一个func()多了什么好处。但是traits可以定义特化版本。下面就定一个针对原生指针的特化版本:
//偏特化版本--->迭代器是一个原生指针
template <class T>
struct iterator_traits<T*>
{
    typedef T value_type;
};
当T为int*之类的原生指针时,仍然可以通过traits将其value_type获取。至此第二小节中的问题基本解决了。
再考虑指向常量的指针(pointer to const)。
假设此时类型为const int*,如果直接使用上面的特化版本会导致获取的类型为const int而不是我们期望的int。因此要再针对指向常量的指针定义一个特化版本:
//偏特化版本--->迭代器是一个pointer-to-const
template <class T>
struct iterator_traits<const T*>
{
    typedef T value_type;
};
如此通过traits技法,无论是迭代器还是原生指针,都能正确的“萃取”出其相应的value type。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值