源码解析
接口定义
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)上下文。