facebook folly库for_each的源码解析(上)

源码解析

接口定义

folly for_each的定义很简单,就是一个模板函数,源码如下:

 

template <typename Sequence, typename Func>

constexpr Func for_each(Sequence && sequence, Func func);

 

其中有两个模板参数:一个是Range类型,即一个可以用来被for_each迭代的stl容器类型或者tuple类型;另一个是Func类型,用于在迭代的过程中对元素进行处理的自定义函数,当然也可以是lambda函数。

for_each 的返回类型为自定义函数类型。

 

对于Func自定义函数类型,应该满足以下类型定义:

 

template <typename T>

for_each_detail::LoopControl (*)(T ele )

 

或者

 

template <typename T>

for_each_detail::LoopControl (*)(T ele, size_t index )

 

 

或者干脆返回类型为void,如:

 

template <typename T>

void (*)(T ele )

 

或者

template <typename T>

void (*)(T ele, size_t index )

 

对于前者,for_each仅对元素进行迭代处理,而对于后者,for_each在调用的时候,同时也把该元素的位置信息index也传递进来。其中返回类型for_each_detail::LoopControl用来告诉for_each是否需要继续迭代还是中途退出迭代。for_each_detail::LoopControl 的两个返回类型定义如下:

constexpr auto loop_break = for_each_detail::LoopControl::BREAK;

constexpr auto loop_continue = for_each_detail::LoopControl::CONTINUE;

 

 

 

folly for_each 还提供了fetch函数,用来从集合类型或者tuple类型中根据索引index提取其中的元素。源码如下:

template <typename Sequence, typename Index>

constexpr decltype(auto) fetch(Sequence&& sequence, Index&& index)

 

 

fetch的两个模板参数中的Sequence和for_each一样,没有特别之处, Index指定了所要获取的元素的索引。对于数组类型,index对应的数组的下标;对于map类型,index对应的map的key;对于tuple类型,index对应的是元素在tuple中定义的位置, 最终通过tuple.get<index>()来获取对应的元素。

当然要能够根据指定的index对元素进行fetch,相对于for_each,fetch对集合类型有额外的要求,就是能够通过operator[](index)来获取集合中的元素。

 

例如,如果对list<int>类型调用fetch(index),那么编译器就会产生如下错误信息:

 

error: no match for ‘operator[]’ (operand types are ‘std::list<int>’ and ‘int’)

 

这个是很容易理解的。

 

fetch的返回类型是decltype(auto),即通过C++编译器自动进行类型推断。

 

 

实现

for_each的实现,源代码如下:

 

template <typename Sequence, typename Func>

constexpr Func for_each(Sequence&& sequence, Func func) {

  namespace fed = for_each_detail;

1using tag = fed::SequenceTag<Sequence>;

2fed::for_each_impl(tag{}, std::forward<Sequence>(sequence), func);

  return func;

}

 

1tag利用C++元编程技术对Sequence类型进行标注,是集合类型RangeTag,还是元组类型TupleTag。

2for_each_impl根据tag的类型,最终匹配到不同的特化版本,来实现对sequnce中的元素进行迭代。

 

 

 

那么SequenceTag是怎么来识别出模板中的Sequence类型是集合还是元组的呢?

接着看一下代码:

template <typename Sequence>

using SequenceTag =

std::conditional_t<IsRange<void, Sequence>::value, RangeTag, TupleTag>;

 

 

std::conditional_t是一个条件模板,在c++ 11的stl库的<traits>中有定义,如下:

 

template< bool B, class T, class F >

struct conditional;

 

template< bool B, class T, class F >

using conditional_t = typename conditional<B,T,F>::type;

 

 

即判断B是否为true, 如果true, 那么conditional的type定义为模板参数T对应的类型,否则就定义为F类型。
结合SequenceTag的定义就不难理解了,如果Sequence用IsRange判断是Range类型的,那么SequenceTag就是RangeTag类型,否则为TupleTag类型。

 

那么IsRange又是怎么判断Sequence是否Range类型的呢?

再看IsRange的定义:

 

1template <typename, typename T>

struct IsRange : std::false_type {};

 

2template <typename T>

struct IsRange<EnableIfRange<T>, T> : std::true_type {};

 

 

IsRange有两个版本,需要根据类型来进行推断到底最终使用哪个版本。

先看第一个版本的定义,很明显,实际上是非对应集合类型会应用到这个版本,IsRange从std::false_type继承过来,其value为false。

再看第二个版本的定义, 也不难推断出,这个版本对应的是集合类型,IsRange从std::true_type中继承过来,其value为true。

 

那么IsRange<void, Sequence>的模板调用如何来匹配到哪个IsRange的特化版本的呢?

这就需要看EnableIfRange<T>是否推断出来void,能够和IsRange<void,Sequence>的void正好匹配。继续看EnableIfRange的定义:

 

template <typename Type, typename T = typename std::decay<Type>::type>

using EnableIfRange = void_t<

    decltype(adl::adl_begin(std::declval<T>())),

    decltype(adl::adl_end(std::declval<T>()))>;

 

通过分析void_t的代码,可以了解到,void_t会在类型T存在begin和end方法函数的时候,返回void类型。这样子就会匹配到第二个版本的IsRange,其value为true。

 

在分析void_t之前先来看看它的两个模板参数,因为两个模板参数形式一样,只要理解了第一个,那么第二个自然也就好理解了,第一个模板参数为:

decltype(adl::adl_begin(std::declval<T>()))

 

其中std::declval<T>() 就是实例化一个类型为T的引用类型,以便于应用到decltype的表达式中而无需经过类的构造函数进行实例化,这个可以从std::declval的c++11 stl标准库中对它的定义,可以了解:

Converts any type T to a reference type, making it possible to use member functions in decltype expressions without the need to go through constructors.

 

接下去是adl::adl_begin的定义:

template <typename Type>

auto adl_begin(Type&& instance) -> decltype(begin(instance)) {

  return begin(instance);

}

 

这个adl_begin函数就是通过调用std::begin来间接调用Type类型的begin方法,如果Type类型的begin方法不存在怎么办?那么会导致decltype的类型推断无法进行,进而导致EnableIfRange推导失败,所以只能匹配到第一个版本的IfRange。

 

最后,在看看看void_t, 看看c++ 17 stl标准库中的定义:

Utility metafunction that maps a sequence of any types to the type void

翻译过来的意思是在模板元编程中把一个序列的任何类型都映射成void类型。为什么要应设成void类型呢?接着看下面的定义:

This metafunction is used in template metaprogramming to detect ill-formed types in SFINAE context

翻译过来的意思是这个元函数在模板元编程中用来探测不合规范的SFINAE即匹配失败不是错误, Substitution Failure Is Not An Error上下文。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农心语

您的鼓励是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值