LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

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 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1