diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..ae76261eadc6e26b37902c123a3146f0cba94aef --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +SortIncludes: false +Standard: Cpp03 diff --git a/.github/workflows/jrl-cmakemodules.yml b/.github/workflows/jrl-cmakemodules.yml index 0de0d5ec9df241637d1649606131f99d492d80ac..4ae45554ce5e3c24bab9a59ba1e6b093a7b02da0 100644 --- a/.github/workflows/jrl-cmakemodules.yml +++ b/.github/workflows/jrl-cmakemodules.yml @@ -1,5 +1,8 @@ name: JRL-cmakemodules on: [push,pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: with-submodules: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c3c182b817a53bdf4107f9923df5f0e70e3b3a07..f48a606c5c034c0d9f9f4abaf80ad42870252e10 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,6 +1,9 @@ name: Check build on linux on: ["push", "pull_request"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: test: diff --git a/.github/workflows/macos-linux-conda.yml b/.github/workflows/macos-linux-conda.yml index f40513a54acae8d30824212f6d83ff5899e5e0bb..edc1784413efb81fcd2f91bd24206acf329e5810 100644 --- a/.github/workflows/macos-linux-conda.yml +++ b/.github/workflows/macos-linux-conda.yml @@ -1,6 +1,9 @@ name: Conda-CI on: [push,pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: eigenpy-conda: diff --git a/.github/workflows/reloc.yml b/.github/workflows/reloc.yml index ae271202955ecf63b93e7201fada793e071c1f21..6f317f7b51194d3dbde1980d92a62046482923f4 100644 --- a/.github/workflows/reloc.yml +++ b/.github/workflows/reloc.yml @@ -1,6 +1,9 @@ name: Ensure relocatable on: [push,pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: relocatable: diff --git a/.github/workflows/ros_ci.yml b/.github/workflows/ros_ci.yml index cdcaacf19880220d27ec0070a2f5bd1cb87ddd9a..330f8379a0374d123b0a85f74c2752d33f53e9fa 100644 --- a/.github/workflows/ros_ci.yml +++ b/.github/workflows/ros_ci.yml @@ -5,6 +5,9 @@ name: ROS-CI # This determines when this workflow is run on: [push, pull_request] # on all pushes and PRs +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: CI: diff --git a/.github/workflows/windows-conda.yml b/.github/workflows/windows-conda.yml index 3ab602b3fd56241d6f42f3b45367010dad571e49..74d923fd74bf33f73345c07c5468bcdc956d6c43 100644 --- a/.github/workflows/windows-conda.yml +++ b/.github/workflows/windows-conda.yml @@ -1,5 +1,8 @@ name: Build Eigenpy on Windows via Conda on: [push,pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: diff --git a/CHANGELOG.md b/CHANGELOG.md index af4445e4a90140152f3ba1b547e6f72df9a57c0d..2de93919c7b1e412d3caf4948e200a6a13dda762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Support for `Eigen::SparseMatrix` types ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426)) +- Support for `boost::variant` types with `BoostVariantConvertor` ([#430](https://github.com/stack-of-tasks/eigenpy/pull/430)) ### Fixed - Fix the issue of missing exposition of Eigen types with __int64 scalar type ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426)) diff --git a/include/eigenpy/boost-variant.hpp b/include/eigenpy/boost-variant.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f3735a953fe4f718041b457921c74a816a2bda40 --- /dev/null +++ b/include/eigenpy/boost-variant.hpp @@ -0,0 +1,136 @@ +// +// Copyright (c) 2024 INRIA +// + +#ifndef __eigenpy_utils_boost_variant_hpp__ +#define __eigenpy_utils_boost_variant_hpp__ + +#include <boost/python.hpp> +#include <boost/variant.hpp> +#include <boost/mpl/for_each.hpp> + +namespace eigenpy { + +namespace details { + +/// Convert boost::variant<class...> alternative to a Python object. +/// This converter copy the alternative. +template <typename Variant> +struct BoostVariantValueToObject : boost::static_visitor<PyObject*> { + typedef Variant variant_type; + + static result_type convert(const variant_type& gm) { + return apply_visitor(BoostVariantValueToObject(), gm); + } + + template <typename T> + result_type operator()(T& t) const { + return boost::python::incref(boost::python::object(t).ptr()); + } +}; + +/// Convert boost::variant<class...> alternative reference to a Python object. +/// This converter return the alternative reference. +/// The code that create the reference holder is taken from +/// \see boost::python::to_python_indirect. +template <typename Variant> +struct BoostVariantRefToObject : boost::static_visitor<PyObject*> { + typedef Variant variant_type; + + static result_type convert(const variant_type& gm) { + return apply_visitor(BoostVariantRefToObject(), gm); + } + + template <typename T> + result_type operator()(T& t) const { + return boost::python::detail::make_reference_holder::execute(&t); + } +}; + +/// Converter used in \see ReturnInternalBoostVariant. +/// This is inspired by \see boost::python::reference_existing_object. +/// It will call \see BoostVariantRefToObject to extract the alternative +/// reference. +template <typename Variant> +struct BoostVariantConverter { + typedef Variant variant_type; + + template <class T> + struct apply { + struct type { + PyObject* operator()(const variant_type& gm) const { + return BoostVariantRefToObject<variant_type>::convert(gm); + } + +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + PyTypeObject const* get_pytype() const { + return boost::python::converter::registered_pytype< + variant_type>::get_pytype(); + } +#endif + }; + }; +}; + +/// Declare a variant alternative implicitly convertible to the variant +template <typename Variant> +struct BoostVariantImplicitlyConvertible { + typedef Variant variant_type; + + template <class T> + void operator()(T) { + boost::python::implicitly_convertible<T, variant_type>(); + } +}; + +} // namespace details + +/// Variant of \see boost::python::return_internal_reference that +/// extract boost::variant<class...> alternative reference before +/// converting it into a PyObject +template <typename Variant> +struct ReturnInternalBoostVariant : boost::python::return_internal_reference<> { + typedef Variant variant_type; + + typedef details::BoostVariantConverter<variant_type> result_converter; +}; + +/// Define a defaults converter to convert a boost::variant alternative to a +/// Python object by copy and to convert implicitly an alternative to a +/// boost::variant. +/// +/// Example: +/// +/// typedef boost::variant<Struct1, Struct2> MyVariant; +/// struct VariantHolder { +/// MyVariant variant; +/// }; +/// ... +/// void expose() { +/// boost::python::class_<Struct1>("Struct1", bp::init<>()); +/// boost::python::class_<Struct2>("Struct1", bp::init<>()) +/// typedef eigenpy::BoostVariantConvertor<MyVariant> Convertor; +/// Convertor::registration(); +/// +/// boost::python::class_<VariantHolder>("VariantHolder", bp::init<>()) +/// .add_property("variant", +/// bp::make_getter(&VariantHolder::variant, +/// Convertor::return_internal_reference()), +/// bp::make_setter(&VariantHolder::variant)); +/// } +template <typename Variant> +struct BoostVariantConvertor { + typedef Variant variant_type; + typedef ReturnInternalBoostVariant<variant_type> return_internal_reference; + + static void registration() { + typedef details::BoostVariantValueToObject<variant_type> variant_to_value; + boost::python::to_python_converter<variant_type, variant_to_value>(); + boost::mpl::for_each<typename variant_type::types>( + details::BoostVariantImplicitlyConvertible<variant_type>()); + } +}; + +} // namespace eigenpy + +#endif // ifndef __eigenpy_utils_boost_variant_hpp__ diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index dab2bfba7596b7ceff0927e21e622b62ba1e8e22..c65c6cf1995d436de6a614ca141551e3f851d4f8 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -45,6 +45,7 @@ add_lib_unit_test(std_vector) add_lib_unit_test(std_array) add_lib_unit_test(std_pair) add_lib_unit_test(user_struct) +add_lib_unit_test(boost_variant) function(config_bind_optional tagname opttype) set(MODNAME bind_optional_${tagname}) @@ -132,6 +133,10 @@ add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py" "python;unittest") set_tests_properties("py-user-struct" PROPERTIES DEPENDS ${PYWRAP}) +add_python_unit_test("py-boost-variant" "unittest/python/test_boost_variant.py" + "python;unittest") +set_tests_properties("py-boost-variant" PROPERTIES DEPENDS ${PYWRAP}) + add_python_unit_test("py-bind-virtual" "unittest/python/test_bind_virtual.py" "python;unittest") set_tests_properties("py-bind-virtual" PROPERTIES DEPENDS ${PYWRAP}) diff --git a/unittest/boost_variant.cpp b/unittest/boost_variant.cpp new file mode 100644 index 0000000000000000000000000000000000000000..096b69a489f532a006c31b745bcb75fe10eaa043 --- /dev/null +++ b/unittest/boost_variant.cpp @@ -0,0 +1,41 @@ +/// @file +/// @copyright Copyright 2024 CNRS INRIA + +#include <eigenpy/eigenpy.hpp> +#include <eigenpy/boost-variant.hpp> + +namespace bp = boost::python; + +struct V1 { + int v; +}; +struct V2 { + char v; +}; +typedef boost::variant<V1, V2> MyVariant; + +MyVariant make_variant() { return V1(); } + +struct VariantHolder { + MyVariant variant; +}; + +BOOST_PYTHON_MODULE(boost_variant) { + using namespace eigenpy; + + enableEigenPy(); + + bp::class_<V1>("V1", bp::init<>()).def_readwrite("v", &V1::v); + bp::class_<V2>("V2", bp::init<>()).def_readwrite("v", &V2::v); + + typedef eigenpy::BoostVariantConvertor<MyVariant> Convertor; + Convertor::registration(); + + bp::def("make_variant", make_variant); + + boost::python::class_<VariantHolder>("VariantHolder", bp::init<>()) + .add_property("variant", + bp::make_getter(&VariantHolder::variant, + Convertor::return_internal_reference()), + bp::make_setter(&VariantHolder::variant)); +} diff --git a/unittest/python/test_boost_variant.py b/unittest/python/test_boost_variant.py new file mode 100644 index 0000000000000000000000000000000000000000..023b940bdc91150e97f80d9d96f142cca73fff54 --- /dev/null +++ b/unittest/python/test_boost_variant.py @@ -0,0 +1,36 @@ +from boost_variant import V1, V2, VariantHolder, make_variant + +variant = make_variant() +assert isinstance(variant, V1) + +v1 = V1() +v1.v = 10 + +v2 = V2() +v2.v = "c" + +variant_holder = VariantHolder() + +# Test copy from variant alternative V1 to non initialized variant +variant_holder.variant = v1 +assert isinstance(variant_holder.variant, V1) +assert variant_holder.variant.v == v1.v + +# variant_holder.variant is a copy of v1 +variant_holder.variant.v = 11 +assert v1.v != variant_holder.variant.v + +# Test variant_holder.variant return by reference +# v1 reference variant_holder.variant +v1 = variant_holder.variant +variant_holder.variant.v = 100 +assert variant_holder.variant.v == 100 +assert v1.v == 100 +v1.v = 1000 +assert variant_holder.variant.v == 1000 +assert v1.v == 1000 + +# Test with the second alternative type +variant_holder.variant = v2 +assert isinstance(variant_holder.variant, V2) +assert variant_holder.variant.v == v2.v