From 1aa8dd17451108871ade6b1ea86d6bcea9d3215a Mon Sep 17 00:00:00 2001 From: Roland Dreier Date: Tue, 11 Jun 2019 13:25:12 -0700 Subject: [PATCH] Fix assertion failure for unions (#1685) (#1709) In def_readonly and def_readwrite, there is an assertion that the member comes from the class or a base class: static_assert(std::is_base_of::value, "..."); However, if C and type are the same type, is_base_of will still only be true if they are the same _non-union_ type. This means we can't define accessors for the members of a union type because of this assertion. Update the assertion to test std::is_same::value || std::is_base_of::value which will allow union types, or members of base classes. Also add a basic unit test for accessing unions. --- include/pybind11/pybind11.h | 4 ++-- tests/CMakeLists.txt | 1 + tests/test_union.cpp | 22 ++++++++++++++++++++++ tests/test_union.py | 8 ++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/test_union.cpp create mode 100644 tests/test_union.py diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index ca0b1a709..f1d91c788 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1185,7 +1185,7 @@ public: template class_ &def_readwrite(const char *name, D C::*pm, const Extra&... extra) { - static_assert(std::is_base_of::value, "def_readwrite() requires a class member (or base class member)"); + static_assert(std::is_same::value || std::is_base_of::value, "def_readwrite() requires a class member (or base class member)"); cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this)), fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this)); def_property(name, fget, fset, return_value_policy::reference_internal, extra...); @@ -1194,7 +1194,7 @@ public: template class_ &def_readonly(const char *name, const D C::*pm, const Extra& ...extra) { - static_assert(std::is_base_of::value, "def_readonly() requires a class member (or base class member)"); + static_assert(std::is_same::value || std::is_base_of::value, "def_readonly() requires a class member (or base class member)"); cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this)); def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); return *this; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a31d5b8b7..42640d0b3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,6 +59,7 @@ set(PYBIND11_TEST_FILES test_stl.cpp test_stl_binders.cpp test_tagbased_polymorphic.cpp + test_union.cpp test_virtual_functions.cpp ) diff --git a/tests/test_union.cpp b/tests/test_union.cpp new file mode 100644 index 000000000..7b98ea216 --- /dev/null +++ b/tests/test_union.cpp @@ -0,0 +1,22 @@ +/* + tests/test_class.cpp -- test py::class_ definitions and basic functionality + + Copyright (c) 2019 Roland Dreier + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "pybind11_tests.h" + +TEST_SUBMODULE(union_, m) { + union TestUnion { + int value_int; + unsigned value_uint; + }; + + py::class_(m, "TestUnion") + .def(py::init<>()) + .def_readonly("as_int", &TestUnion::value_int) + .def_readwrite("as_uint", &TestUnion::value_uint); +} diff --git a/tests/test_union.py b/tests/test_union.py new file mode 100644 index 000000000..e1866e701 --- /dev/null +++ b/tests/test_union.py @@ -0,0 +1,8 @@ +from pybind11_tests import union_ as m + + +def test_union(): + instance = m.TestUnion() + + instance.as_uint = 10 + assert instance.as_int == 10