C++14尝鲜:在C++中实现LINQ

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
*/
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值