ファンクターをメソッドとして公開することは公式にはサポートされていません。サポートされているアプローチは、メンバー関数に委譲する非メンバー関数を公開することです。ただし、これにより大量の定型コードが生成される可能性があります。
私が知る限り、Boost.Python の実装は、インスタンスをpython::object
メソッドとして公開できるため、ファンクターを明示的に排除していません。ただし、Boost.Python では、メソッドとして公開されるオブジェクトのタイプにいくつかの要件があります。
- ファンクターは CopyConstructible です。
- ファンクタは呼び出し可能です。つまり、インスタンス
o
を呼び出すことができますo(a1, a2, a3)
。
- 呼び出し署名は、実行時にメタデータとして利用できる必要があります。Boost.Python は
boost::python::detail::get_signature()
関数を呼び出して、このメタデータを取得します。メタデータは、適切な呼び出しをセットアップするため、および Python から C++ にディスパッチするために内部的に使用されます。
後者の要件は、複雑になるところです。すぐにはわからない何らかの理由で、Boost.Python はget_signature()
修飾された ID を介して呼び出し、引数に依存するルックアップを防ぎます。したがって、すべての候補はget_signature()
、呼び出し元のテンプレートの定義コンテキストの前に宣言する必要があります。たとえば、そのためのオーバーロードのみget_signature()
が考慮され、それを呼び出すテンプレートの定義の前に宣言されたものclass_
( 、def()
、およびなど) が考慮されmake_function()
ます。この動作を説明するには、Boost.Python でファンクターを有効にするときに、Boost.Pythonget_signature()
を含める前にオーバーロードを提供するか、署名を表すメタシーケンスを明示的に に提供する必要がありますmake_function()
。
ファンクターのサポートを有効にする例と、ガードをサポートするファンクターを提供する例を見てみましょう。C++11 の機能を使用しないことにしました。そのため、可変個引数テンプレートで削減できる定型コードがいくつかあります。さらに、すべての例では、2 つの非メンバー関数と 2 つのメンバー関数を持つspam
クラスを提供する同じモデルを使用します。
/// @brief Mockup class with member functions.
class spam
{
public:
void action()
{
std::cout << "spam::action()" << std::endl;
}
int times_two(int x)
{
std::cout << "spam::times_two()" << std::endl;
return 2 * x;
}
};
// Mockup non-member functions.
void action()
{
std::cout << "action()" << std::endl;
}
int times_two(int x)
{
std::cout << "times_two()" << std::endl;
return 2 * x;
}
有効にするboost::function
Boost.Function の優先構文を使用する場合、署名を Boost.Python の要件を満たすメタデータに分解するには、Boost.FunctionTypesを使用します。boost::function
以下は、Boost.Python メソッドとしてファンクターを公開できるようにする完全な例です。
#include <iostream>
#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
namespace boost {
namespace python {
namespace detail {
// get_signature overloads must be declared before including
// boost/python.hpp. The declaration must be visible at the
// point of definition of various Boost.Python templates during
// the first phase of two phase lookup. Boost.Python invokes the
// get_signature function via qualified-id, thus ADL is disabled.
/// @brief Get the signature of a boost::function.
template <typename Signature>
inline typename boost::function_types::components<Signature>::type
get_signature(boost::function<Signature>&, void* = 0)
{
return typename boost::function_types::components<Signature>::type();
}
} // namespace detail
} // namespace python
} // namespace boost
#include <boost/python.hpp>
/// @brief Mockup class with member functions.
class spam
{
public:
void action()
{
std::cout << "spam::action()" << std::endl;
}
int times_two(int x)
{
std::cout << "spam::times_two()" << std::endl;
return 2 * x;
}
};
// Mockup non-member functions.
void action()
{
std::cout << "action()" << std::endl;
}
int times_two(int x)
{
std::cout << "times_two()" << std::endl;
return 2 * x;
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose class and member-function.
python::class_<spam>("Spam")
.def("action", &spam::action)
.def("times_two", boost::function<int(spam&, int)>(
&spam::times_two))
;
// Expose non-member function.
python::def("action", &action);
python::def("times_two", boost::function<int()>(
boost::bind(×_two, 21)));
}
そしてその使用法:
>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
spam::times_two()
10
>>> example.action()
action()
>>> example.times_two()
times_two()
42
メンバー関数を呼び出すファンクターを提供する場合、提供されるシグネチャは非メンバー関数と同等である必要があります。この場合、int(spam::*)(int)
となりint(spam&, int)
ます。
// ...
.def("times_two", boost::function<int(spam&, int)>(
&spam::times_two))
;
また、引数は でファンクターにバインドできますboost::bind
。たとえば、すでにファンクターにバインドされているため、呼び出しexample.times_two()
で引数を提供する必要はありません。21
python::def("times_two", boost::function<int()>(
boost::bind(×_two, 21)));
ガード付きのカスタム ファンクター
上記の例を拡張すると、Boost.Python でカスタム ファンクター タイプを使用できるようになります。RAIIguarded_function
を使用し、 RAII オブジェクトの存続期間中にラップされた関数のみを呼び出す、というファンクターを作成しましょう。
/// @brief Functor that will invoke a function while holding a guard.
/// Upon returning from the function, the guard is released.
template <typename Signature,
typename Guard>
class guarded_function
{
public:
typedef typename boost::function_types::result_type<Signature>::type
result_type;
template <typename Fn>
guarded_function(Fn fn)
: fn_(fn)
{}
result_type operator()()
{
Guard g;
return fn_();
}
// ... overloads for operator()
private:
boost::function<Signature> fn_;
};
はguarded_function
、Pythonwith
ステートメントと同様のセマンティクスを提供します。したがって、Boost.Python API の名前の選択を維持するために、with()
C++ 関数はファンクターを作成する方法を提供します。
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn>
boost::python::object
with(Fn fn)
{
return boost::python::make_function(
guarded_function<Guard, Fn>(fn), ...);
}
これにより、邪魔にならない方法でガード付きで実行される関数を公開できます。
class no_gil; // Guard
// ...
.def("times_two", with<no_gil>(&spam::times_two))
;
さらに、with()
関数は関数シグネチャを推測する機能を提供し、メタデータ シグネチャを明示的に Boost.Python にオーバーロードするのではなく提供できるようにしますboost::python::detail::get_signature()
。
2 つの RAII タイプを使用した完全な例を次に示します。
no_gil
: コンストラクタで GIL を解放し、デストラクタで GIL を再取得します。
echo_guard
: コンストラクタとデストラクタで出力します。
#include <iostream>
#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
#include <boost/function_types/function_type.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/python.hpp>
#include <boost/tuple/tuple.hpp>
namespace detail {
/// @brief Functor that will invoke a function while holding a guard.
/// Upon returning from the function, the guard is released.
template <typename Signature,
typename Guard>
class guarded_function
{
public:
typedef typename boost::function_types::result_type<Signature>::type
result_type;
template <typename Fn>
guarded_function(Fn fn)
: fn_(fn)
{}
result_type operator()()
{
Guard g;
return fn_();
}
template <typename A1>
result_type operator()(A1 a1)
{
Guard g;
return fn_(a1);
}
template <typename A1, typename A2>
result_type operator()(A1 a1, A2 a2)
{
Guard g;
return fn_(a1, a2);
}
private:
boost::function<Signature> fn_;
};
/// @brief Provides signature type.
template <typename Signature>
struct mpl_signature
{
typedef typename boost::function_types::components<Signature>::type type;
};
// Support boost::function.
template <typename Signature>
struct mpl_signature<boost::function<Signature> >:
public mpl_signature<Signature>
{};
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn,
typename Policy>
boost::python::object with_aux(Fn fn, const Policy& policy)
{
// Obtain the components of the Fn. This will decompose non-member
// and member functions into an mpl sequence.
// R (*)(A1) => R, A1
// R (C::*)(A1) => R, C*, A1
typedef typename mpl_signature<Fn>::type mpl_signature_type;
// Synthesize the components into a function type. This process
// causes member functions to require the instance argument.
// This is necessary because member functions will be explicitly
// provided the 'self' argument.
// R, A1 => R (*)(A1)
// R, C*, A1 => R (*)(C*, A1)
typedef typename boost::function_types::function_type<
mpl_signature_type>::type signature_type;
// Create a callable boost::python::object that delegates to the
// guarded_function.
return boost::python::make_function(
guarded_function<signature_type, Guard>(fn),
policy, mpl_signature_type());
}
} // namespace detail
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn,
typename Policy>
boost::python::object with(const Fn& fn, const Policy& policy)
{
return detail::with_aux<Guard>(fn, policy);
}
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn>
boost::python::object with(const Fn& fn)
{
return with<Guard>(fn, boost::python::default_call_policies());
}
/// @brief Mockup class with member functions.
class spam
{
public:
void action()
{
std::cout << "spam::action()" << std::endl;
}
int times_two(int x)
{
std::cout << "spam::times_two()" << std::endl;
return 2 * x;
}
};
// Mockup non-member functions.
void action()
{
std::cout << "action()" << std::endl;
}
int times_two(int x)
{
std::cout << "times_two()" << std::endl;
return 2 * x;
}
/// @brief Guard that will unlock the GIL upon construction, and
/// reacquire it upon destruction.
struct no_gil
{
public:
no_gil() { state_ = PyEval_SaveThread();
std::cout << "no_gil()" << std::endl; }
~no_gil() { std::cout << "~no_gil()" << std::endl;
PyEval_RestoreThread(state_); }
private:
PyThreadState* state_;
};
/// @brief Guard that prints to std::cout.
struct echo_guard
{
echo_guard() { std::cout << "echo_guard()" << std::endl; }
~echo_guard() { std::cout << "~echo_guard()" << std::endl; }
};
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose class and member-function.
python::class_<spam>("Spam")
.def("action", &spam::action)
.def("times_two", with<no_gil>(&spam::times_two))
;
// Expose non-member function.
python::def("action", &action);
python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
×_two));
}
そしてその使用法:
>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
no_gil()
spam::times_two()
~no_gil()
10
>>> example.action()
action()
>>> example.times_two(21)
no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42
次のようなコンテナー タイプを使用して複数のガードを提供する方法に注意してboost::tuple
ください。
python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
×_two));
Python で呼び出すとexample.times_two(21)
、次の出力が生成されます。
no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42