std::function
は nullptr
を渡す事で, 空の関数オブジェクトを構築する事が出来る. また, 同様に代入演算子に対して nullptr
を渡す事でもまた, 空の関数オブジェクトとなる.
#include <utility>
#include <functional>
int main()
{
using namespace std;
using f = function< void() >;
f x( nullptr );
x = nullptr;
}
また boost::function
は同様の機能を 0
を引数として渡す事で実装している.
#include <utility>
#include <boost/function.hpp>
int main()
{
using namespace std;
using f = boost::function< void() >;
f x( 0 );
x = 0;
}
std::functional
は std::nullptr&&
型の値を渡した場合も同様である.
#include <utility>
#include <functional>
int main()
{
using namespace std;
using f = function< void() >;
f x( move( nullptr ) );
x = move( nullptr );
}
ところが boost::function
は int&&
を渡した場合ビルドエラーとなる. 以下のコードはビルドエラーとなる.
#include <utility>
#include <boost/function.hpp>
int main()
{
using namespace std;
using f = boost::function< void() >;
f x( move( 0 ) );
x = move( 0 );
}
これは boost::function
のコンストラクタ及び代入演算子において, 0 による初期化を行う引数の型が実際には int
ではないために, オーバーロード解決に失敗するためである.
boost::function
のコンストラクタに 0
が渡された時, 以下のコンストラクタでコンパイラはテンプレート引数 Functor
を int
に推論する. 1
template<typename Functor>
function(
Functor f
, typename boost::enable_if_c<
!( is_integral<Functor>::value ), int>::type = 0
);
引数の型が Functor const&
でも Functor&&
でもなく Functor
である事に注目して欲しい.
上記の疑似コードでは省略したが, Functor
の型が整数型であった場合, SFINAE によって上記のコンストラクタはオーバーロードの候補から除外され, メンバ型 clear_type
のポインタ型を引数としたコンストラクタが呼ばれる. そして注目すべき事に, コンパイラは clear_type*
への暗黙の型変換について 0
のみを通し 1
や std::move( 0 )
は通さない 2 のである. これがコンパイラがオーバーロード解決に失敗する理由である.
Boost.Functon が nullptr
ではなく 0
によって空にする仕様になっている理由はまず, 設計された当初に nullptr
が無かったためと思われる.
次に, 0
で空になる事を明示している 3 にも関わらず, clear_type*
を引数としている理由だが, これは前述の様に 1
等のリテラルを渡した時, 実行時ではなくコンパイル時にエラーにするためであろう.
この分岐先の関数は, マクロ BOOST_NO_SFINAE
が定義されている時, 引数の型が int
の関数に置き換えられる. そしてこの関数は内部で 0
である事を条件としてアサートを行っている. この事からも 0
以外の値を受け取った時, エラーにしようという意図が読み取れる.
態々 BOOST_NO_SFINAE
の有無によって分岐している理由は, 0
を引数として受け取った時, SFINAE の無い処理系において clear_type*
型と関数ポインタとの分岐が不可能であるためと思われる.
よって, この問題を修正するためには「 0
で初期化する方法の他に nullptr
でも初期化可能とする仕様を追加する」か「 0
以外の int
型のリテラルが渡された時コンパイル時エラーにする事を諦め, int
型のコンストラクタを用意し, 実行時アサートで妥協する」かの二者択一となる.
一応「ユーザ側で clear_type*
を渡す」という手段もあるが, やや冗長である. この方法は Boost.Optional における boost::none
等が採用している.