読者です 読者をやめる 読者になる 読者になる

そろそろ result_of について

result_of とは

呼び出し可能な型と引数の型から,呼び出した場合の戻り値の型を導出するメタ関数です.例を示しましょう.

typedef int (*char_to_int)(char);

int digit_to_int(char c) { return c - '0'; }

// char_to_int 型に,char 型の引数を渡した時の戻り値を求める
boost::result_of<char_to_int(char)>::type /* == int */ i = (&digit_to_int)('6');

boost::result_of のほかに,C++0x で導入される std::result_of がありますが,ここでは主に Boost のものについて述べることにします.
まず使い方の詳細について,呼び出す側と呼び出される側(関数オブジェクトを実装する側)に分けて説明していきます.

使い方 (呼び出す側)

上に示したように,呼び出す型を F,引数の型を A1, A2, ... としたときに,boost::result_of::type によって戻り値の型を得ることができます.F にはしばしば関数オブジェクト型を与えることになるでしょう.

std::plus<int> plus;
boost::result_of<std::plus<int>(int, int)>::type /* == int */ i = plus(23, 42);

呼び出す型には,関数オブジェクトのほか,関数へのポインタ,関数への参照,メンバ関数へのポインタなどが使えます.関数オブジェクトへの参照や関数そのものを指定することはできません.
また,引数の型としては,引数が lvalue のときは参照型を,rvalue のときは非参照型を渡すようにします.

struct F; // 関数オブジェクト型

F const f = {};

int i = 42;

// lvalue を渡す場合
boost::result_of<F const(int &)>::type v1 = f(i);

// rvalue を渡す場合
boost::result_of<F const(int)>::type v2 = f(42);

F ではなく F const (== f の型) を渡している点にも注目してください.operator() を const の有無でオーバーロードできることを考えれば,const-ness には注意を払うべきです.

使い方 (呼び出される側)

ただ operator() を宣言しただけで,result_of に対応した関数オブジェクトになるわけではありません.result_of が戻り値を見つけるための目印を用意する必要があります.
最も簡単な方法は,クラス内に型 result_type を定義しておくことです.

struct T_digit_to_int
{
    typedef int result_type; // 戻り値の型

    int operator()(char c) const
    {
        return c - '0';
    }
};

T_digit_to_int const digit_to_int = {};

boost::result_of<T_digit_to_int const(char)>::type /* == int */ i = digit_to_int('6');

引数の型によって戻り値の型を変える場合は,クラス内にクラステンプレート result を定義します.この result に F(A1, A2, ...) がそのまま渡されてくるので,戻り値を計算して type として定義します.

// 2 倍する関数オブジェクト
struct T_double_
{
    template <class>
    struct result;

    template <class F, class A>
    struct result<F(A)> // クラステンプレートの部分特殊化を使う
    {
        // 引数の型から参照と const を取り除いて戻り値の型とする
        typedef typename
            boost::remove_const<
                typename boost::remove_reference<A>::type
            >::type
        type;
    };

    template <class A>
    A operator()(A const &a) const
    {
        return a + a;
    }
};

T_double_ const double_ = {};

boost::result_of<T_double_ const(std::string)>::type /* == std::string */ s = double_(std::string("abc"));
// s == "abcabc"

呼び出す側の使い方でも述べたように,引数の型には参照型が渡される可能性があることに注意します.
また,引数が与えられない場合,result は決して使われません (result_of は void を返します).この場合は,boost::result_of を直接特殊化します.
さて,簡単に使い方を見てきましたが,いくつか注意すべき点がありました.それらについて考察しておきましょう.

注意すべき点

呼び出す型に関数型を指定できない

result_of という形は,1 引数を取るクラステンプレート result_of に「引数 A1, A2, ... を取り F を返す関数」F(A1, A2, ...) を与えたものになっています.さて,関数を返す関数は存在しません.したがって,F を関数とすることはできないのです.
追記 (7/18) :
F が関数の場合でもその戻り値を求められるようにするには,

typedef typename boost::mpl::if_<boost::is_function<F>, F *, F>::type fun_t;

または

typedef typename boost::decay<F>::type fun_t;

*1によって関数ポインタ (関数への参照でも可) に変換してから,result_of に渡すようにします.

引数が lvalue のときは参照型を渡さなければならない

A 型の lvalue を与える場合と A const 型の lvalue を与える場合とでは,別のオーバーロードを呼び出す可能性があります.しかし,F(A1) と F(A1 const) は全く同じ関数型です (引数の const 指定は関数の型に影響しません).したがって,参照を付けてこれらを区別します.
rvalue を渡す場合は,通常 T const & で受けるためにその const-ness は問題になりません.したがって,素直に非参照型を渡して大丈夫です.*2

result_of が void を返す

正確には,result_type がなく,result_of の特殊化もされていない場合に void を返すようになっています.なぜでしょうか.
それを考えるために,operator() を転送する reference_wrapper を書いてみましょう.簡単のため,result_of には対応させません.

template <class F>
struct reference_wrapper
{
    F *m_pf;

    reference_wrapper(F &f)
      : m_pf(boost::addressof(f))
    {}

    typename boost::result_of<F()>::type operator()() const
    {
        return (*m_pf)();
    }

    template <class A1>
    typename boost::result_of<F(A1 &)>::type operator()(A1 &a1) const
    {
        return (*m_pf)(a1);
    }

    // ...
};

ここで注目すべきは,引数を取らない operator() です.これはテンプレートではないため*3,戻り値の型 boost::result_of::type はクラスのインスタンス化に伴って計算されます.一方で,F は必ずしも引数なしで呼び出せる型とは限らず,その場合に boost::result_of::type が存在しないと,クラスのインスタンス化時点でエラーになってしまいます.そこで,boost::result_of::type にデフォルト値 void を与えるという workaround がなされたのです.
この workaround は完璧ではありませんが,概ね期待通りに動作します.C++0x では,variadic templates によって引数なしの呼び出しをテンプレートに吸収できるため,この問題を回避できます.

decltype を使う

decltype があれば,容易に戻り値の型を導出できるはずです.BOOST_RESULT_OF_USE_DECLTYPE マクロを定義しておくと,decltype を使用した型推論を行います.

struct T_double_
{
    template <class A>
    A operator()(A const &a) const
    {
        return a + a;
    }
};

T_double_ const double_ = {};

#include <string>

#define BOOST_RESULT_OF_USE_DECLTYPE
#include <boost/utility/result_of.hpp>

int main()
{
    boost::result_of<T_double_ const(std::string)>::type s = double_(std::string("abc"));
    // C++0x なら auto が使えます
}

ただし,これは関数ポインタとメンバ関数ポインタには decltype を使わないようになっている点,また呼び出す型と引数の型の lvalue を元に戻り値を導出しようとする点で C++0x の std::result_of と異なります.また,SFINAE に対応しないという悪い点で std::result_of と同じになっているため,それほどおすすめはできません.次のような定義を用意して使う方が良いと思います.

template <class, class = void>
struct result_of_aux
{};

template <class F, class ...Args>
struct result_of_aux<F(Args...), decltype(std::declval<F>()(std::declval<Args>()...), void())>
{
    typedef decltype(std::declval<F>()(std::declval<Args>()...)) type;
};

template <class Sig>
struct result_of
  : result_of_aux<Sig>
{};

*1:std::decay は cv 修飾を除去するため,ここでは使えません

*2:C++0x の std::result_of を使う場合は,rvalue reference を付けるべきです

*3:テンプレートにすることはできますが,テンプレート引数を明示する必要が生じ,一般性を損ないます