にゃははー

はへらー

[boost|std]::bindではまったこと

多分よく訓練されたバインダー達はこんな程度の引っかかり方はしないだろうけど私は嵌った。
つまり以下のコードだ。

#include <iostream>
#include <functional>
#include <boost/bind.hpp>

struct some_functor
{
	typedef bool result_type;
	template < typename Pred >
	bool operator()( Pred pred )
	{ return pred( 1 ); }
};

int main()
{
	std::cout << std::boolalpha
	  << boost::bind(
	       some_functor()
	     , boost::bind( std::less< int >(), 0, _1 )
	     )()
	  << std::endl;
}

さて、そもそもbindいらなくねとかそんなことは言うな。おっと逸れた。
で、みんなはこれの結果はどうなると思われるだろうか。
まぁ普通に考えて true と表示されて終わると思うだろう。
が、世の中そんな甘くはない。上のコードはコンパイルエラーだ。

私は最初ネストされたbindは素直にカリー化されたファンクタをひとつ上のファンクタに投げてくれるものだと思ってた。
しかし実体は違った。ネストされた内部まで評価されてしまう。つまり上のネストしているバインダは1引数を受け取るということだ。
この挙動はC++0xのstd::bindでも同様だった。

え?バインダをネストすることなんかない?

あぁそうだよ俺もそう思ってた。だがなネストしたんだよ!!!!!!

で、愚痴っててもしょうがないのでどうするかというと、Boost.Bindの場合は

<< boost::bind(
     some_functor()
   , boost::cref( boost::bind( std::less< int >(), 0, _1 ) )
   )()

と内側のboost::bindをBoost.Refで包んでやればいい。boost::reference_wrapperのラップは解かれるが、その内側まで評価はされないためバインダがそのまま投げられる。
本来はあまり良くない使い方だけど仕方ない...というのもこの外側のbindを(std|boost)::functionなどで持ってしまうと内側のbindに対してdangling pointerとなってしまう。
子のような使い方は余程の事情がない限りするべきではないので注意されたい。

std::bindの場合はもっと厄介だ。
標準ライブラリはboost::reference_wrapperについて何も知らないからboost::crefで包んでもboost::reference_wrapperで包まれたままのファンクタが関数に投げられる。
まぁうまい具合に展開されるようになっていたりすればいいが、templateなどを使っている場合などどうにもならない場合、オーバーロードを用意するのも馬鹿らしい。

ここまで来て「なぜstd::crefを使わないんんだ。boost::bindの時はboost::crefを使ったし、標準でstd::bindはstd::reference_wrapperを解くだろ」と思われた方、おしい。
std::cref( std:bind( ... はコンパイルエラーだ。

というのも

template < typename T >
reference_wrapper< const T >
cref( const T && ) = delete;

だからだ。

これはC++0xで導入されたrvalue referenceを明示的にエラーにすることでdangling pointerを防ぐ機構だ。
これ自体は非常に素晴らしい。

とはいえ、どうしたものか。私が今書いてるのはBoost 1.33.1とかいう古典とgcc 4.1.2とかいう旧約聖書を前提としている。Boost.Build v2にglob-treeがないんだよ!!!!
おっと、また逸れた。何が言いたいかというと今のところ問題ないのだが別のところでstd::bindを使ってまた嵌ったらどうしようかということだ。

で、ここまで読んで「Flastプギャーーーーーーあいつあれ知らないのーーwwwマジうけるんですけど----www」って方いたらコメント欄にどうすればいいか書いてください。
あとboostjpの https://sites.google.com/site/boostjp/tips/partial_eval にも追加しといてください。書きたくければ私が書くのでせめてコメント欄で私を罵ってください。

---- 07/29 04:03 追記 ----
http://ideone.com/pRtZw
boost::protect!そんなものが! ということでBoostについては結論が出ましたね。boost:crefを使うとかやめましょう。

で、コンパイルエラーがideoneでもで続けてますが、これはBoostのバージョンが古いことに起因するっぽいです。
boost::bindもboost::protectも

template < typename A1 >
result_type operator()( A1 &a1 );
template < typename A1 >
result_type operator()( A1 &a1 ) const;

となってて、prvalueについては型が合わないというものです。いつからかはわかりませんが最近のBoostでは修正されています。

---- 07/29 15:10 追記 ----
指摘された誤解されそうな部分を追記ではなく編集という形で修正しましたがどうですかね,,,,

あと、twiterとかでの反応を見るにboost::protectに相当するものは標準ライブラリにはなさそうです。
すばるさんがVariadic templatesを使って書いてくれたのを使うっきゃ無いようです。ー>トラックバックから辿ってください