cpplinq
如今在C++中LINQ已经有了众多实现,其中实现度最高,更新最及时,文档最完整的当推LINQ for C++ (cpplinq)。
以下的示例代码使用C++14再现了cpplinq的主要实现技巧。
#include <iostream>
#include <vector>
template<typename TIterator>
struct from_range
{
// range接口的成员类型
// 其含义为C++容器所包含数据的值类型
using value_type = typename std::iterator_traits<TIterator>::value_type;
// from_range内含三个指向数据的迭代器
// 当前迭代器,其次迭代器,末尾迭代器
TIterator current, upcoming, end;
// from_range的构造器
// 当前迭代器指向容器的开头
// 其次迭代器指向容器的开头
// 末尾迭代器指向容器的末尾
from_range(TIterator begin, TIterator end)
: current(begin), upcoming(begin), end(end) {}
// range接口的成员方法
// 重载>>操作符,该操作符接收一个build对象作为右操作符
// 该操作符将自身传给builder对象的build方法,通过调用该方法实现对range的转换
template<typename TRangeBuilder>
decltype(auto) operator>>(TRangeBuilder builder) const { return builder.build(*this); }
// front方法,返回当前迭代器所指向的数据,即当前数据
decltype(auto) front() const { return *current; }
// next方法,当前数据不存在则返回false,若存在则准备当前数据并返回true
// 具体实现为
// 1. 若其次迭代器与末尾迭代器的值相等,则其次迭代器已经指向容器末尾
// 判断当前数据不存在,返回false
// 2. 若其次迭代器与末尾迭代器的值不等,则其次迭代器尚未指向容器末尾
// 将其次迭代器的值赋予当前迭代器,递增其次迭代器,令其指向下一个数据
// 判断当前数据存在,返回true
bool next() { return upcoming == end ? false : (current = upcoming++, true); }
};
template<typename TRange, typename TPredicate>
struct where_range
{
// range接口的成员类型
// 其含义为转换源range所包含数据的值类型
using value_type = typename TRange::value_type;
// where_range内含两个数据成员
// 转换源range,谓词(过滤条件)
TRange range;
TPredicate predicate;
// where_range的构造器
where_range(TRange range, TPredicate predicate)
: range(range), predicate(predicate) {}
// range接口的成员方法
// 重载>>操作符,该操作符接收一个build对象作为右操作符
// 该操作符将自身传给builder对象的build方法,通过调用该方法实现对range的转换
template<typename TRangeBuilder>
decltype(auto) operator>>(TRangeBuilder builder) const { return builder.build(*this); }
// front方法,返回转换源range的当前数据
decltype(auto) front() const { return range.front(); }
// next方法,当前数据不存在则返回false,若存在则准备当前数据并返回true
// 具体实现为
// 扫描数据源range直至找到符合过滤条件的下一个数据
// 若该数据存在,则当前数据存在,返回true
// 否则当前数据不存在,返回false
bool next(){
while(range.next())
if(predicate(range.front()))
return true;
return false;
}
};
template<typename TPredicate>
struct where_builder
{
// where_builder只包含一个成员:谓词(过滤条件)
TPredicate predicate;
// where_builder的构造器
where_builder(TPredicate predicate) : predicate(predicate) {}
// builder接口的成员方法
// build方法,生成并返回where_range对象
template<typename TRange>
auto build(TRange range){
return where_range<TRange, TPredicate>(range, predicate);
}
};
struct to_vector_builder
{
// builder接口的成员方法
// build方法,生成并返回C++容器
// 具体实现为
// 扫描数据源range
// 将扫描所得到的数据依次插入到C++容器中
template<typename TRange>
auto build(TRange range){
std::vector<TRange::value_type> result;
while(range.next())
result.push_back(range.front());
return result;
}
};
// LINQ操作符from,将C++容器转换成from_range对象
template<typename TContainer>
auto from(TContainer const& container)
{
return from_range<decltype(std::begin(container))>(std::begin(container), std::end(container));
}
// LINQ操作符where,生成并返回where_builder对象
template<typename TPredicate>
auto where(TPredicate predicate) { return where_builder<TPredicate>(predicate); }
// LINQ操作符to_vector,生成并返回to_vector_builder对象
auto to_vector() { return to_vector_builder(); }
int main()
{
int a[] = {1, 2, 3, 4};
// 找出数组a中所有的偶数,将结果存放到一个vector对象中
auto v = from(a) >> where([](int n){return n % 2 == 0;}) >> to_vector();
for(int i : v) std::cout << i << std::endl;
}
/*
2
4
*/
示例代码总共实现了cpplinq中的三个LINQ操作符from,where以及to_vector。
- LINQ操作符from是cpplinq世界的入口(之一),负责将C++容器转换为cpplinq的range。
- LINQ操作符where是cpplinq世界的转换器(之一),按照一定的条件来过滤cpplinq的range中所包含的数据。
- LINQ操作符to_vector是cpplinq世界的出口(之一),负责将cpplinq的range转换为C++容器。
range和builder
range和builder是cpplinq的两种基本元素。
range是cpplinq中的数据结构类,内部包含实际数据或者指向实际数据的迭代器。
- range可以通过from操作符与to_vector操作符和C++容器实现相互转换。也可以通过where操作符实现自身种类的转换。
- 示例代码总共实现了两种range:from_range和where_range。
- from_range由C++容器通过from操作符转换生成,内部包含指向C++容器中数据的迭代器。
- where_range由作为转换源的range通过where操作符转换生成,内部包含转换源range以及where操作符所包含的谓词(过滤条件)。
builder是cpplinq中的行为类,内部的build方法用于实现对range的转换。
- 通过调用builder类中的build方法,可以转换range的种类,也可以将range转换为C++容器。
- 示例代码总共实现了两种builder:where_builder和to_vector_builder。
- where_builder由where操作符生成,内部的build方法用于实现where操作符的功能:将range转换为where_range。
- to_vector_builder由to_vector操作符生成,内部的build方法用于实现to_vector操作符的功能:将range转换为C++容器。
典型用例分析
from_range >> where_builder >> to_vector_builder
- from_range重载了>>操作符,该操作符负责调用右操作数builder对象的build方法,并将自身作为参数传给该方法。
- where_builder的build方法返回where_range,因此from_range >> where_builder的结果应该是一个where_range。
- where_range同样重载了>>操作符,该操作符负责调用右操作数builder对象的build方法,并将自身作为参数传给该方法。
- to_vector_builder的build方法返回C++容器,因此where_range >> to_vector_builder的结果应该是一个C++容器。
示例代码分析
auto v = from(a) >> where([](int n){return n % 2 == 0;}) >> to_vector();
- from(a)将数组a转换为from_range对象。
- where([](int n){return n % 2 == 0;})生成where_builder对象,该对象内部包含一个是否为偶数的谓词(过滤条件)。
- to_vector()生成to_vector_builder对象。
- 由以上分析可知该段代码将生成典型用例分析中的调用关系,因此返回值v应该是一个C++容器。
range接口
cpplinq中所有的range类都实现了range接口。
range接口由一个成员类型和三个成员方法组成。
/// range接口
struct xxx_range
{
// range接口的成员类型
// 其含义为range类的front方法所返回的当前数据的值类型
using value_type = typename ...;
// range接口的成员方法
// 重载>>操作符,该操作符接收一个build对象作为右操作符
// 该操作符将自身传给builder对象的build方法,通过调用该方法实现对range的转换
template<typename TRangeBuilder>
decltype(auto) operator>>(TRangeBuilder builder) const { return builder.build(*this); }
// front方法,返回当前数据
decltype(auto) front() const { return ...; }
// next方法,若当前数据不存在则返回false,若存在则准备当前数据并返回true
bool next() { return ...; }
};
builder接口
cpplinq中所有的builder类都实现了builder接口。
builder接口只包含一个成员方法。
/// builder接口
struct xxx_builder
{
// builder接口的成员方法
// build方法,转换range的种类或者生成C++容器
template<typename TRange>
auto build(TRange range){ return ...; }
};
select操作符
下面让我们来实现LINQ操作符select。
select操作符生成select_builder对象,该对象负责将range转换为select_range。
template<typename TRange, typename TSelector>
struct select_range
{
// range接口的成员类型
// 其含义为转换源range所包含数据经过映射后的值类型
using value_type = typename decltype(std::declval<TSelector>()(std::declval<TRange::value_type>()));
// select_range内含两个数据成员
// 转换源range,映射函数selector
TRange range;
TSelector selector;
// select_range的构造器
select_range(TRange range, TSelector selector)
: range(range), selector(selector) {}
// range接口的成员方法
// 重载>>操作符,该操作符接收一个build对象作为右操作符
// 该操作符将自身传给builder对象的build方法,通过调用该方法实现对range的转换
template<typename TRangeBuilder>
decltype(auto) operator>>(TRangeBuilder builder) const { return builder.build(*this); }
// front方法,返回对转换源range的当前数据进行映射后的结果
decltype(auto) front() const { return selector(range.front()); }
// next方法,当前数据不存在则返回false,若存在则准备当前数据并返回true
// 具体实现为
// 调用数据源range的next方法
bool next() { return range.next(); }
};
template<typename TSelector>
struct select_builder
{
// select_builder只包含一个成员:映射函数selector
TSelector selector;
// select_builder的构造器
select_builder(TSelector selector) : selector(selector) {}
// builder接口的成员方法
// build方法,生成并返回select_range对象
template<typename TRange>
auto build(TRange range){
return select_range<TRange, TSelector>(range, selector);
}
};
// LINQ操作符select,生成并返回select_builder对象
template<typename TSelector>
auto select(TSelector selector) { return select_builder<TSelector>(selector); }
改写main函数以便测试select操作符。
int main()
{
int a[] = {1, 2, 3, 4};
// 找出数组a中所有的偶数,乘以2,将结果存放到一个vector对象中
auto v = from(a) >> where([](int n){return n % 2 == 0;}) >> select([](int n){return n * 2;}) >> to_vector();
for(int i : v) std::cout << i << std::endl;
}
/*
4
8
*/