FOREACHマクロ〜自作に挑戦!

Eric Niebler 氏のBOOST_FOREACHの中身の解説ページを勉強し、1週間がかりで、自分なりのFOREACHマクロ(右辺値、リテラル非対応)を完成させることができたのでメモ。同サイトでは、3項演算子の重要性がとてもわかりやすく書かれていた。また、最後のマクロ定義の部分は、BOOST_FOREACHのソースを見ながら理解した。それは前の日記にも記した通りだ。
本マクロ作成過程でとくに勉強となったのは以下のよ〜なことだ。

  • anybase, anyテンプレートのペアを使った、任意型オブジェクトのラッピングテクニック
  • type2type(ModernC++にもあったね)の具体的な使い方(done(), next()関数など。)
  • 型変換演算子の存在(operator bool() みたいな書き方)
  • 関数テンプレートなのにを直接指定させるやり方(下記のorgcast関数みたいな書き方)
  • stlコンテナの、::reference 型定義。コンテナ中身の型がとれる!(また、下記orgcast_hでは、これにより大幅高速化が実現されている)
  • あとは前の前の日記で書いた、3項演算子を使った、式を評価せずに型情報のみ抽出する方法
  • if文を多様した、自動スコープ変数の定義方法(anybaseはboolの0を返す!いやはや、裏技だ)

//
// NURS_FOREACH (version 0.7)   (C) 2008-2010 nurs 
// 2010.10.30 become VC10 ready.
//
#include <iostream>
using namespace std;

#pragma once
#include <iostream>
using namespace std;





// 任意型保持クラス
template < class T > class type2type{};
template < class T > type2type< T > type2type_h( const T& t ){ 
	return type2type< T >();
}

// 任意型のtype2typeを返すクラス
class anytype{
public: 
  template< class T > operator type2type< T >(){
		return type2type< T >(); 
	};
};

// 任意型ラッパークラス
class anybase{
public: 
   operator bool() const { return 0; }
};
template< class T > class any: public anybase
{
public: 
  any( const T& t ):m_t( t ){};
	mutable T m_t;
};
template< class T > any< typename T::const_iterator > get_begin( const T& t ){ 
	return any< T::const_iterator >( t.begin() ); 
}
template< class T > any< typename T::const_iterator > get_end( const T& t ){ 
	return any< T::const_iterator >( t.end() );
}
template< class T > any< typename T::iterator > get_begin( T& t ){ 
	return any< T::iterator >( t.begin() ); 
}
template< class T > any< typename T::iterator > get_end( T& t ){ 
	return any< T::iterator >( t.end() );
}
template< class T > typename T& orgcast ( anybase& t ){
	// 関数テンプレートですが、Tを渡して使用します。
	return static_cast< any<T>& >( t ).m_t; 
}
template< class T > typename T::reference orgcast_h(  
anybase& t, type2type<T>& ){
	// 実体ではなく、参照を返すから速い!
	return *orgcast< typename T::iterator >( t );
}
template< class T > bool pocforeach_done( anybase& a, anybase& b, type2type<T>& ){
	typedef typename T::const_iterator typ;
	return orgcast< typ >( a ) == orgcast< typ >( b );
}
template< class T > void poc_next( anybase& a, type2type<T>& ){
	++orgcast< T::const_iterator >( a );
}

#define extract_type(x) \
	(!0)? anytype(): type2type_h(x) \


#define NURS_FOREACH( CONT, VAR )	\
	if( anybase& ittako = get_begin( CONT ) ){}else \
	if( anybase& ite = get_end( CONT ) ){}else \
	for( bool goon=!0; \
			goon && !pocforeach_done( ittako, ite, extract_type(CONT) ); \
			poc_next( ittako, extract_type(CONT) ) \
	) \
	if( goon=0 ){}else	\
	for( VAR= orgcast_h( ittako, extract_type(CONT) ); !goon; goon=!0 ) \



本マクロの制作にあたっては途中、参照とすべきところを実体受け取りで書いてしまったり、type2type使うべきところを式形式で直接渡してしまったりして、せっかく一通り完成しても、プログラムが落ちてしまったり、またBOOST_FOREACHに比べて倍の処理時間がかかってしまったりと、いろいろ苦労があったが、最後は頭よりむしろ手のほうを動かしながらなんとか完成を見た。
しかしまだなお、(見ればわかる、そして実験してみればわかるが)式CONTが2回直接評価されてしまうという問題がある。boost_foreachだと、1回しか評価されない!にゅー、どうやってるんだ。。(begin()とend()はそれぞれCONT式評価せなとれんのちゃうんかい…汗。)しかも、コンテナがconstだったりそうでなかった場合の考察もあまり深くしてないし…。
実際に動かしてみて、速度など測定してみた。以下に実験コードもメモしておく。結果としては、通常の使い方ではBOOST_FOREACHと同じパフォーマンスが得られた。

#include <windows.h>
#include <mmsystem.h>

#include <iostream>
#include <vector>
using namespace std;

#include "boost/foreach.hpp" 
#pragma comment( lib, "winmm.lib" )


int main( int argc, char* argv[] )
{
	//const int n = 1000000;
	const int n = 10000;
	vector< int > iv;
	for( int i=0; i<n; ++i ){
		iv.push_back( i );
	}// i

	timeBeginPeriod( 1 );//時間計測を1msの精度で	
	{
		DWORD s, e;
		int sum=0;
		{
			s = timeGetTime();// 
BOOST_FOREACH( const int& i, iv ){// ★★BOOST_FOREACH
				sum += i*i;
			}// i
			e = timeGetTime();//
		}	
		cout << e - s<<"ms"<<sum<< endl;// 32ms
	}
	{
		DWORD s, e;
		int sum=0;
		{
			s = timeGetTime();// 
NURS_FOREACH( const int& i, iv ){// ★★NURS_FOREACH
				sum += i*i;
			}// i
			e = timeGetTime();//
		}	
		cout << e - s<<"ms"<<sum<< endl;// 32ms
	}
	{
		DWORD s, e;
		int sum=0;
		{
			s = timeGetTime();//
vector< int >::iterator it;// ★★普通のfor文
vector< int >::iterator ie=iv.end();
for( it=iv.begin(); it!=ie; ++it ){
				sum+= (*it)*(*it);
			}// it
			e = timeGetTime();
		}	
		cout << e - s<<"ms" <<sum <<endl;// 21ms
	}
	timeEndPeriod( 1 );
	return 0;
}


最後に本マクロは、単なる現在の私の備忘録なので、動作の一切を保証するものではない。あくまでこれは私的なメモに過ぎず、また更なる改良の余地がありまくることも忘れてはならない。
ところで、どうしてFOREACHマクロなど作ろうとしているのだろうか。もし、VC++2005以降を使っているのであれば、言語拡張としての for each( .. in .. ) { 構文が使えるから、FOREACHの自作なんて今すぐやめて、そっち使ったらええわ。