diff --git a/include/boost/python/detail/nullary_function_adaptor.hpp b/include/boost/python/detail/nullary_function_adaptor.hpp new file mode 100755 index 00000000..010ed7d0 --- /dev/null +++ b/include/boost/python/detail/nullary_function_adaptor.hpp @@ -0,0 +1,41 @@ +// Copyright David Abrahams 2003. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. +#ifndef NULLARY_FUNCTION_ADAPTOR_DWA2003824_HPP +# define NULLARY_FUNCTION_ADAPTOR_DWA2003824_HPP + +namespace boost { namespace python { namespace detail { + +// nullary_function_adaptor -- a class template which ignores its +// arguments and calls a nullary function instead. Used for building +// error-reporting functions, c.f. pure_virtual +template +struct nullary_function_adaptor +{ + nullary_function_adaptor(NullaryFunction fn) + : m_fn(fn) + {} + + void operator()() const { m_fn(); } + +# define BOOST_PP_LOCAL_MACRO(i) \ + template \ + void operator()( \ + BOOST_PP_ENUM_BINARY_PARAMS_Z(1, i, A, const& BOOST_PP_INTERCEPT) \ + ) const \ + { \ + m_fn(); \ + } + +# define BOOST_PP_LOCAL_LIMITS (1, BOOST_PYTHON_MAX_ARITY) +# include BOOST_PP_LOCAL_ITERATE() + + private: + NullaryFunction m_fn; +}; + +}}} // namespace boost::python::detail + +#endif // NULLARY_FUNCTION_ADAPTOR_DWA2003824_HPP diff --git a/include/boost/python/pure_virtual.hpp b/include/boost/python/pure_virtual.hpp new file mode 100755 index 00000000..04a7de2b --- /dev/null +++ b/include/boost/python/pure_virtual.hpp @@ -0,0 +1,127 @@ +// Copyright David Abrahams 2003. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. +#ifndef PURE_VIRTUAL_DWA2003810_HPP +# define PURE_VIRTUAL_DWA2003810_HPP + +# include +# include +# include +# include +# include + +# include + +namespace boost { namespace python { + +namespace detail +{ + // + // { Helpers for pure_virtual_visitor, below. + // + + // Raises a Python RuntimeError reporting that a pure virtual + // function was called. + void BOOST_PYTHON_DECL pure_virtual_called(); + + // Replace the two front elements of S with T1 and T2 + template + struct replace_front2 + { + // Metafunction forwarding seemed to confound vc6 + typedef typename mpl::push_front< + typename mpl::push_front< + typename mpl::pop_front< + typename mpl::pop_front< + S + >::type + >::type + , T2 + >::type + , T1 + >::type type; + }; + + // Given an MPL sequence representing a signature, returns a new MPL + // sequence whose return type is replaced by void, and whose first + // argument is replaced by C&. + template + typename replace_front2::type + error_signature(S BOOST_APPEND_EXPLICIT_TEMPLATE_TYPE(C)) + { + typedef typename replace_front2::type r; + return r(); + } + + // + // } Helpers for pure_virtual_visitor + // + + // + // A def_visitor which defines a method as usual, then adds a + // corresponding function which raises a "pure virtual called" + // exception unless it's been overridden. + // + template + struct pure_virtual_visitor + : def_visitor > + { + pure_virtual_visitor(PointerToMemberFunction pmf) + : m_pmf(pmf) + {} + + private: + friend class def_visitor_access; + + template + void visit(C_& c, char const* name, Options& options) const + { + // This should probably be a nicer error message + BOOST_STATIC_ASSERT(!Options::has_default_implementation); + + // Add the virtual function dispatcher + c.def( + name + , m_pmf + , options.doc() + , options.keywords() + , options.policies() + ); + + typedef BOOST_DEDUCED_TYPENAME C_::select_holder::held_type held_t; + + // Add the default implementation which raises the exception + c.def( + name + , detail::make_function_aux( + detail::nullary_function_adaptor(pure_virtual_called) + , default_call_policies() + , args_from_python() + , detail::error_signature(detail::get_signature(m_pmf)) + ) + ); + } + + private: // data members + PointerToMemberFunction m_pmf; + }; +} + +// +// Passed a pointer to member function, generates a def_visitor which +// creates a method that only dispatches to Python if the function has +// been overridden, either in C++ or in Python, raising a "pure +// virtual called" exception otherwise. +// +template +detail::pure_virtual_visitor +pure_virtual(PointerToMemberFunction pmf) +{ + return detail::pure_virtual_visitor(pmf); +} + +}} // namespace boost::python + +#endif // PURE_VIRTUAL_DWA2003810_HPP diff --git a/src/object/function.cpp b/src/object/function.cpp index 768a7ed1..3f6cdcf6 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -627,7 +627,7 @@ handle<> function_handle_impl(py_function const& f) new function(f, 0, 0))); } -} +} // namespace objects namespace detail { @@ -639,6 +639,11 @@ namespace detail f , keyword_range(&k,&k)); } + void BOOST_PYTHON_DECL pure_virtual_called() + { + PyErr_SetString(PyExc_RuntimeError, "Pure virtual function called"); + throw_error_already_set(); + } } -}} // namespace boost::python::objects +}} // namespace boost::python diff --git a/test/polymorphism.cpp b/test/polymorphism.cpp index 551c9024..48d269fb 100644 --- a/test/polymorphism.cpp +++ b/test/polymorphism.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,27 @@ struct Callback PyObject* mSelf; }; +struct P +{ + virtual ~P(){} + virtual std::string f() = 0; +}; + +struct PCallback : P, Callback +{ + PCallback (PyObject* self) : Callback(self) {} + + std::string f() + { + return call_method(mSelf, "f"); + } +}; + +struct Q : P +{ + std::string f() { return "Q::f()"; } +}; + struct A { virtual ~A(){} @@ -44,7 +66,7 @@ struct ACallback : A, Callback struct B : A { - virtual std::string f() { return "B::f()"; } + virtual std::string f() { return "B::f()"; } }; struct C : A @@ -127,6 +149,13 @@ BOOST_PYTHON_MODULE_INIT(polymorphism_ext) def("factory", factory, return_value_policy()); def("call_f", call_f); + + class_("P") + .def("f", pure_virtual(&P::f)) + ; + + class_ >("Q") + ; } //#include "module_tail.cpp" diff --git a/test/polymorphism.py b/test/polymorphism.py index d9bd763b..6d518c99 100644 --- a/test/polymorphism.py +++ b/test/polymorphism.py @@ -46,7 +46,22 @@ class PolymorphTest(unittest.TestCase): def test_wrapper_downcast(self): a = pass_a(D()) self.failUnlessEqual('D::g()', a.g()) - + + def test_pure_virtual(self): + p = P() + self.assertRaises(RuntimeError, p.f) + + q = Q() + self.failUnlessEqual ('Q::f()', q.f()) + + class R(P): + def f(self): + return 'R.f' + + r = R() + self.failUnlessEqual ('R.f', r.f()) + + if __name__ == "__main__": # remove the option which upsets unittest