C++でLINQ

cpplinqというC++11でLinqライクな事が出来るライブラリを使ってみた、という内容です。
なので若干釣り記事です。

コンパイラGCC 4.8.1(MinGW)を使用しています。

cpplinq概要

cpplinqでは、操作用のクラスへRange受け取った後、>>演算子によってクエリ式を実行していき、最終的にlistやvectorで結果を受け取ります、
内部ではクエリ式ごとに専用のクラスへと変換されていきますが、必要分のメンバしか持たない為ある程度コストが抑えられています。
また、C#のLINGと同じく評価されるまでクエリ式が実行されない遅延評価が実装されてます。

auto v = {1, 2, 3};
auto q = from(v) >> where([](const int &x){ cout << "hoge"; return true; }); // まだ未評価なので、hogeは出力されない

結果出力(ここでhogeが3回出力)
for (const auto &x : q >> to_vector()) { ... }

サンプルコード

基本的な使い方はこうなります。

#include <iostream>
#include "cpplinq.hpp"

namespace cpplinq {
	// 固定長配列用
	// @note : from_arrayだとconstな固定長配列を使えない & 関数名を統一する為のヘルパ関数
	template<typename TValueArray>
	CPPLINQ_INLINEMETHOD detail::from_range<typename detail::get_array_properties<TValueArray>::iterator_type> from(
			TValueArray & a 
		) throw ()
	{
		typedef typename std::remove_const<typename std::remove_reference<decltype(*std::begin(a))>::type>::type type;
		return from_iterators(
			const_cast<type *>(std::begin(a)),
			const_cast<type *>(std::end(a))
		);
	}
	
	// initializer_list用
	// @note : initialize_listを直接渡せるようにする為のヘルパ関数
	//         usage : cppinq::from({1, 2, 3, 4, 5})
	template<typename T>
	CPPLINQ_INLINEMETHOD detail::from_range<typename std::initializer_list<T>::const_iterator> from(
			std::initializer_list<T> const &  list
		) throw ()
	{
		return from_iterators(
			list.begin(),
			list.end()
		);
	}
}

int main() {
	
	using namespace cpplinq;
	{
		auto v = {1, 2, 3, 4 ,5};
		// {12.56f, 15.7f}
		auto q = from(v)
			>> where ([](const int &x) { return x > 3; })
			>> select([](const int &x) -> float { return x * 3.14f; })
			>> where ([](const float &x) { return x > 12.0f; }) // ここでは意味ない
			;
	} {
		// {2, 4}
		auto q = from({1, 2, 3, 4, 5})
			>> where([](const int &x) { return x % 2 == 0; })
			;
	}
	return 0;
}

ラムダ式型推論を使う時

ラムダ式型推論を使いたいのですが、autoはC++11ではまだ未実装です、
decltypeがあるにはありますが、Select後等では型が変わる為適用が難しいです、
型が変わる時は推論しないというのも手ですが、一貫性に欠けます。

template <class Range>
auto _f_rangetype(Range &&rng) ->
	typename std::remove_reference<
		decltype(*std::begin(rng))
	>::type
	;
// 配列要素の型取得
#define rangetype(rng) decltype(_f_rangetype(rng))

const int v[] = {1, 2, 3, 4 ,5};
// {12.56f, 15.7f}になる?
auto q = from(v)
	>> where ([](const rangetype(v) &x) { return x > 3; });
	>> select([](const rangetype(v) &x) -> float { return x * 3.14f; })

	//>> where ([](const rangetype(v) &x) { return x > 12.0f; }) // NG.(12.56fがfalseになる)
	>> where([](const float &x) { return x > 12.0f; }) // ここだけ推論してない
	;

受け取ったクエリのfrontメソッドを使えばdecltypeで推論出来ますが、一貫性を持たせようとすると冗長且つめんどくさいです。

template <class Query>
auto _f_querytype(Query &&q) ->
	typename std::remove_const<
		typename std::remove_reference<
			decltype(q.front())
		>::type
	>::type
	;
// クエリの型取得
#define querytype(q) decltype(_f_querytype(q))

const int v[] = {1, 2, 3, 4 ,5};
// {12.56f, 15.7f}
auto q1 = from(v) >> where([](const rangetype(v) &x) { return x > 3; });
auto q2 = q1 >> select([](const querytype(q1) &x) -> float { return x * 3.14f; });
auto q3 = q2 >> where([](const querytype(q2) &x) { return x > 12.0f; });

なので、C++11ではラムダ式には推論はしない方が良いと思います、どうしても適用したい場合は上記の事に注意しながら実装するって事になるでしょう。

まとめ

C++でもLINQを使いたいって時には凄く役立つライブラリだと思いますがC++11時点ではまだ若干使いづらい面があります、
しかし、C++14になれば非常に使えるライブラリだと思います。