Posted at

boost::function は int 型の右辺値参照型では初期化出来ない

More than 1 year has passed since last update.

std::functionnullptr を渡す事で, 空の関数オブジェクトを構築する事が出来る. また, 同様に代入演算子に対して 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::functionalstd::nullptr&& 型の値を渡した場合も同様である.

#include <utility>

#include <functional>
int main()
{
using namespace std;
using f = function< void() >;
f x( move( nullptr ) );
x = move( nullptr );
}

ところが boost::functionint&& を渡した場合ビルドエラーとなる. 以下のコードはビルドエラーとなる.

#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 が渡された時, 以下のコンストラクタでコンパイラはテンプレート引数 Functorint に推論する. 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 のみを通し 1std::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 等が採用している.





  1. 実際のコードはもう少し複雑である. 



  2. この部分について,規格上の参照箇所が分かる型がいらっしゃったらご指摘願います. 



  3. Boost.Function Tutorial Basic Usage