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の自作なんて今すぐやめて、そっち使ったらええわ。