泛型编程

《C++11/14高级编程:Boost程序库探秘》笔记

本章讨论Boost库中的三个泛型编程用的工具

  • enable_if:在编译器启用或禁用特定的泛型代码
  • call_traits:非标准元函数,计算类型T可能的多种类型,经常被用于函数的入口参数或者返回值类型的计算。
  • concept_check:以库的方式实现了泛型编程中急需的概念检查功能,在标准提供语言级别的概念检查支持之前是唯一可用的工具。
enable_if

enable_if主要用来解决模板函数或模板类的重载解析问题,允许模板函数或模板类仅针对某些特定类型有效,即依据条件启用或禁用某些特化形式,其中一部分已经被收入C++11(头文件<type_traits>)
enable_if库位于名字空间boost,需要包含头文件<boost/core/enable_if.hpp>
类摘要
enable_if库提供了两类元函数,分别是enable_if的“启动”和disable_if的“禁用”系列。
enable_if的类摘要如下:

template<bool B,class T = void> //T缺省值是void类型
struct enable_if_c{
    typedef T type;             //默认返回类型T
};

template<class T>
struct enable_if_c<false,T>{ }; //对false特化,无::type返回

template<class Cond,class T = void>
struct enable_if:
    public enable_if_c<Cond::value,T>{};    //计算元函数Cond

enable_if使用元函数转发技术,计算条件元函数Cond的值,再交给enable_if_c。如果条件为true,那么enable_if/enable_if_c将返回类型T,否则将是一个无返回的元函数。

disable_if与enable_if相似,语义相反。
处理重载函数时编译器要构造所有同名函数的集合,再从中选择一个最恰当的函数,当存在模板函数时,如果模板函数可以被模板实参推演实例化,那么它就是一个候选函数,反之,如果某个参数或返回值类型无效而导致推演失败无法实例化,那么这个模板函数就不是候选函数,但编译器不会认为这是编译错误,这就是著名的SFINAE原则,即“替代失败不是错误”(substitution failure is not an error)

应用于模板函数
enable_if通常需要配合type_traits或者mpl使用,作为模板推演时的控制条件,检查类型T是否满足某些条件。
enable_if可放在函数参数列表的最末尾用作缺省参数,或者是用作返回值,两种形式的效果是相同的,但有的时候只能使用一种形式,比如用于构造函数和析够函数时没有返回值,用于操作符重载时不能变动参数的数量。
比如下面这个print()函数仅在类型是整数时才生效:

template<typename T>
T print(T x,typename enable_if<is_integral<T> >::type* = 0)
{
    cout << "int:" << x << endl;
    return x;
}

enable_if作为缺省参数出现,声明一个无名指针参数,默认值是空指针,这样,当编译器进行模板实例化时,如果T不是整数,那么enable_if不会返回任何类型,实例化失败,从而这个print()被禁用。
返回值用法:

template<typename T>
typename enable_if<is_integral<T>,T>::type print(T x)
{...}

这里,enable_if要传递第二个元参数T,否则,enable_if返回值是void,不符合函数的签名。第二个元参数不一定必须填T,可以在这里再进行元计算,比如使用promote<T>提升T的范围。

应用于模板类
enable_if的启用或禁用模板类偏特化的用法与模板函数用法类似,它需要为类的模板参数列表增加一个额外缺省参数,缺省值是void,再使用enable_if来偏特化。

template<typename T,typename Enable = void> //增加一个模板参数
class demo_class
{...};
template<typename T>    //使用enable_if偏特化
class demo_class<T,typename enable_if<is_arithmetic<T> >::type
{...};

这里demo_class使用enable_if对int、double等算术类型进行了偏特化,简单的可以直接用is_same:

//对string类型特化
template<typename T>    //使用enable_if偏特化
class demo_class<T,typename enable_if<is_same<T,string> >::type
{...};

对比C++11标准
C++11/14标准里的enable_if只是boost.enable_if的一个很小的子集(并且没有disable_if),功能很有限,不能计算元函数,第一个模板参数是一个bool类型,实际上相当于boost::enable_if_c。

call_traits

call_traits是一个很小的泛型工具,它封装了C++中编写函数时可能是“最好的”传递给函数的方式,会自动推导出最高效的传递参数的类型,某种程度上可以说是一个“智能参数类型”。call_traits位于名字空间boost,需要头文件<boost/call_traits.hpp>
类摘要
call_traits是一个返回多个值的非标准元函数:

template <typename T>
struct call_traits
{
public:
    typedef T           value_type;     //T的值类型
    typedef T&          reference;      //T的引用类型
    typedef const T&    const_reference;//T的const引用类型
    typedef some_define param_type;     //T的被调用参数类型
};

用法
在编写函数时,有时候传值比传引用或指针更高效,比如POD类型,而类类型则应该传引用,为了安全,有时候还需要加上const修饰,函数返回时有值和引用的区别,使用call_traits可以对类型T执行元计算,根据C++社区已经达成的共识返回以下四个最高效的类型:

  • value_type:T的“值类型”,通常是T,但对于数组T[N]则退化为const T*,可用于保存值或者以值返回。
  • reference:T的“引用类型”,通常是T&,可用于返回值引用。
  • const_reference:T的“常引用类型”,通常是const T&,可用于返回值引用。
  • param_type:T的“参数类型”,通常是const T&,但对于POD类型或者指针类型则是const T,可用作“最好的”函数参数传递类型。

四个类型中最方便也是最常用的是param_type,用作函数的参数类型。
以编写一个连接两个字符串的函数为例:

string scat(string& s1,string s2)
{ return s1 + s2; }

如果使用的是cout << scat("1","2");就无法通过编译了,必须使用临时变量的形式来写:

cout << scat(string("1"),string("2"));

使用call_traits,则可以使函数接口更加合理且高效:

#include <boost/call_traits.hpp>
#include <boost/type_traits.hpp>
#include <cassert>
#include <iostream>
using namespace boost;

typedef call_traits<std::string> str_traits;

str_traits::value_type scat(str_traits::param_type s1,str_traits::param_type s2)
{
    assert(is_reference<str_traits::param_type>::value);
    assert((is_same<str_traits::param_type,const std::string&>::value));

    return s1 + s2;
}
int main()
{
    std::cout << scat("1","2");
    return 0;
}

对于模板类来说call_traits可以很好推断最合适的模板参数类型,节约定义类型的时间和精力。例如下面代码可以定义一个模板类demo_class,利用call_traits工具,可以容纳任意的类型,包括数组和引用。

#include <boost/call_traits.hpp>
#include <boost/type_traits.hpp>
#include <cassert>
#include <iostream>
using namespace boost;

template<typename T>
class demo_class
{
public:
    typedef typename call_traits<T>::value_type v_type;
    typedef typename call_traits<T>::param_type p_type;
    typedef typename call_traits<T>::reference  r_type;
    typedef typename call_traits<T>::const_reference cr_type;
private:
    v_type v;
public:
    demo_class(p_type p):v(p){}

    v_type value()
    { return v;}

    r_type get()
    { return v;}
};

int main()
{
    int a[3] = {1,2,3};
    demo_class<int[3]> di(a);   //容纳数组类型
    assert(di.value()[0] == 1);

    char c = 'A';
    demo_class<char&> dc(c);
    assert(dc.get() == c);
    return 0;
}

实现原理
call_traits的实现原理是通过使用模板特化技术,针对T&、T*、T[N]等类型做了特殊处理,解决引用的引用和数组的类型问题。

concept_check

泛型编程中使用的“静态多态”,在语义上经常要求类型具有某种特征或者满足某种条件,这些要求通常被称为“概念”,C++中并没有对“概念检查”的支持,Boost的concept_check以库的方式达到了效果,能够在编译出错时给出可读的信息。
concept_check位于名字空间boost,需要头文件<boost/concept_check.hpp>
概念
概念检查的基本工具是宏BOOST_CONCEPT_ASSERT,用法很像静态断言BOOST_STATIC_ASSERT,可以用在任何域:函数域、类域、名字空间域,如果概念不通过则导致编译错误。但BOOST_CONCEPT_ASSERT和静态断言有区别,它不能在宏中使用逻辑运算符(!、&&等),宏的参数必须要用括号括起来,也就是说要双重括号。
concept_check库提供了大量的概念检查类来检查类型是否符合某个概念,这与type_traits库类似,同样是检测类型的属性,只是type_traits检查偏重于C++类型系统,而concept_check偏重类型的功能属性。另外,type_traits提供的是标准的元函数,而concept_check不用与元计算,在多数情况下用来配合检查宏工作。
concept_check用于概念检查元函数可分为如下六个类别:

  • 基本的概念检查:检查整数类型、拷贝构造、缺省构造、赋值函数等的基本概念
  • 函数对象概念检查:检查函数对象的相关概念
  • 标准迭代器概念检查:检查标准库的五种迭代器的分类概念
  • 新式迭代器概念检查:检查Boost定义的九种迭代器的分类概念
  • 容器概念检查:检查容器的相关概念
  • 区间概念检查:检查区间的相关概念
基本概念检查

基本概念检查的功能与type_traits提供的功能相似,包括如下的检查类:

Integer<T>              //检查T是否是内建的整数类型
SignedInteger<T>        //检查T是否是内建的有符号整数类型
UnsignedInteger<T>      //检查T是否是内建的无符号整数类型
Convertible<X,Y>        //检查X是否可转换为Y
Assignable<T>           //检查T是否是可赋值的
DefaultConstructible<T> //检查T是否有缺省构造函数
CopyConstructible<T>    //检查T是否有拷贝构造函数
EqualityComparable<T>   //检查T是否可以进行相等比较
LessThanComparable<T>   //检查T是否可以进行小于比较
Comparable<T>           //检查T是否可以进行所有关系运算

可以实现标准库的min()函数

template<typename T>
T my_min(const T& l,const T& r)
{
    BOOST_CONCEPT_ASSERT((LessThanComparable<T>));  //要求可小于比较
    return (l < r) ? l : r;
}

当没有定义operator<的类传入my_min()时会产生编译错误:

complex<double> cp1,cp2;
my_min(cp1,cp2);
函数对象概念检查

函数对象概念检查主要基于标准库的函数对象定义,模板参数复杂,除了输入要检查的函数对象类型外,还需要输入返回值类型和参数类型。以下是几种常用的检查类,其中F是函数对象类型,R是返回值类型,A和B分别是两个参数类型:

Generator<F,R>          //检查F是否是无参函数对象
UnaryFunction<F,R,A>    //检查F是否是单参函数对象
BinaryFunction<F,R,A,B> //检查F是否是双参函数对象
UnaryPredicate<F,A>     //检查F是否是单参谓词
BinaryPredicate<F,A,B>  //检查F是否是双参谓词
Const_BinaryPredicate<F,A,B>    //检查F是否是const双参谓词

示范这些概念检查类的代码:

BOOST_CONCEPT_ASSERT((UnaryFunction<negate<int>,int,int>));
BOOST_CONCEPT_ASSERT((BinaryFunction<plus<int>,int,int,int>));
标准迭代器概念检查

标准迭代器概念检查完全依据C++标准的定义,概念检查类还定义了value_type、reference、pointer等内部类型,等价于std::iterator_traits。

InputIterator<I>                    //是否是输入迭代器
OutputIterator<I,T>                 //是否后是输出类型T的输出迭代器
ForwardIterator<I>                  //是否是前向迭代器
Mutable_ForwardIterator<I>          //是否可变前向迭代器(即可修改,支持*i++ = *i操作)
BidirectionalIterator<I>            //是否双向迭代器
Mutable_BidirectionalIterator<I>    //是否可变双向迭代器
RandomAccessIterator<I>             //是否随机访问迭代器
Mutable_RandomAccessIterator<I>     //是否可变随机访问迭代器

使用:

//原声指针满足迭代器概念
BOOST_CONCEPT_ASSERT((InputIterator<int*>));
BOOST_CONCEPT_ASSERT((OutputIterator<int*,int>));
BOOST_CONCEPT_ASSERT((RandomAccessIterator<int*>));

//可以从概念检查类获取迭代器的类型定义
assert((is_same<InputIterator<int*>::pointer,
    iterator_traits<int*>::pointer>::value));

//forward_list是C++11提供的单向链表容器,支持前向迭代
BOOST_CONCEPT_ASSERT((ForwardIterator<forward_list<int>::iterator>));
BOOST_CONCEPT_ASSERT((Mutable_ForwardIterator<
                        forward_list<int>::iterator>));

//vector支持双向迭代器和随机访问
typedef vector<int>::iterator I;
BOOST_CONCEPT_ASSERT((BidirectionalIterator<I>));
BOOST_CONCEPT_ASSERT((RandomAccessIterator<I>));
新式迭代器概念检查

iterator库定义了一组新式的迭代器概念,也提供了相应的概念检查类,这些概念检查类位于名字空间boost_concepts(不是boost),需要包含头文件<boost/iterator/iterator_concepts.hpp>

ReadableIteratorConcept<I>          //是否是可读迭代器
WritableIteratorConcept<I,T>        //是否是可写迭代器
SwappableIteratorConcept<I>         //是否是可交换迭代器
LvalueIteratorConcept<I>            //是否是左值迭代器
IncrementableIteratorConcept<I>     //是否是可递增迭代器
SinglePassIteratorConcept<I>        //是否是单遍迭代器
ForwardTraversalConcept<I>          //是否是前向迭代器
BidirectionalTraversalConcept<I>    //是否是双向迭代器
RandomAccessTraversalConcept<I>     //是否是随机访问遍历迭代器
容器概念检查

容器概念检查类检查是否符合标准库的容器定义,这些容器概念检查类都有内部的value_type、reference等概念满足的类型定义。

//基本的容器概念检查类
Container<C>                        //是否满足标准容器定义
Mutable_Container<C>                //是否满足可变容器定义(即可修改元素的值)
ForwardContainer<C>                 //是否可以前向迭代
Mutable_ForwardContainer<C>         //是否满足可变前向迭代容器定义
ReversibleContainer<C>              //是否可以逆向迭代
Mutable_ReversibleContainer<C>      //是否满足可变逆向迭代容器定义
RandomAccessContainer<C>            //是否满足随机访问容器定义
Mutable_RandomAccessContainer<C>    //是否满足可变随机访问容器定义

//概念检查类检查容器的序列类型
Sequence<C>                     //是否是线性序列容器
FrontInsertionSequence<C>       //是否支持序列头插入操作
BackInsertionSequence<C>        //是否支持序列尾插入操作
AssociativeContainer<C>         //是否是关联容器
UniqueAssociativeContainer<C>   //是否不允许重复键
MultipleAssociativeContainer<C> //是否允许重复键
SimpleAssociativeContainer<C>   //是否键即值,即集合类型
PairAssociativeContainer<C>     //是否是键——值关联类型,即映射
SortedAssociativeContainer<C>   //是否是有序
区间概念检查

区间概念检查类检查是否符合区间的概念——类似容器,但是要求比容器低一些。区间概念检查类位于头文件<boost/range/concepts.hpp>。包括以下几种:

SinglePassRangeConcept<R>               //是否是单遍区间
ForwardRangeConcept<R>                  //是否是前向区间
WriteableForwardRangeConcept<R>         //是否是可写前向区间
BidirectionalRangeConcept<R>            //是否是双向区间
WriteableBidirectionalRangeConcept<R>   //是否是可写双向区间
RandomAccessRangeConcept<R>             //是否是随机访问区间
WriteableRandomAccessRangeConcept<R>    //是否是可写随机访问区间
在函数声明中的概念检查

BOOST_CONCEPT_ASSERT在基本的概念检查中很有效果,但是需要在函数的声明中“显式”给出概念检查,BOOST_CONCEPT_ASSERT就无法做到。
concept_check库提供了另一个宏BOOST_CONCEPT_REQUIRES,它可以在模板函数的声明里做概念检查,把检查的时机更向前提一步。
使用BOOST_CONCEPT_REQUIRES需要另外包含头文件<boost/concept/requires.hpp>

template<...>
BOOST_CONCEPT_REQUIRES(
    ((some_check_class 1))  //概念检查类列表开始
    ((some_check_class 2))
    ...
    ((some_check_class N)), //概念检查类列表结束
    (return type)   )       //返回值类型
function_name(...)          //函数名
{...}

BOOST_CONCEPT_REQUIRES只能用在函数声明里,有两个参数,第一个是概念检查类列表,是多个双重括号的序列,第二个是函数的返回类型,也必须用括号括起来。
使用BOOST_CONCEPT_REQUIRES可以把my_min()改写如下形式:

template<typename T>
BOOST_CONCEPT_REQUIRES(
    ((LessThanComparable<T>)),
    (T) )
my_min(const T& l,const T& r)
{ return (l < r) ? l : r; }
概念原型类

为方便测试验证泛型代码,concept_check库提供了一些精确匹配标准库概念的最小类型——称为原型类。
使用概念原型需要包含额外的头文件:&lit;boost/concept_archetype.hpp>
目前concept_check库有基本概念原型、函数对象概念原型和迭代器概念原型,暂时没有容器概念原型。原型类与概念检查类基本一一对应。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值