Allow arbitrary class_ template option ordering
The current pybind11::class_<Type, Holder, Trampoline> fixed template
ordering results in a requirement to repeat the Holder with its default
value (std::unique_ptr<Type>) argument, which is a little bit annoying:
it needs to be specified not because we want to override the default,
but rather because we need to specify the third argument.
This commit removes this limitation by making the class_ template take
the type name plus a parameter pack of options. It then extracts the
first valid holder type and the first subclass type for holder_type and
trampoline type_alias, respectively. (If unfound, both fall back to
their current defaults, `std::unique_ptr<type>` and `type`,
respectively). If any unmatched template arguments are provided, a
static assertion fails.
What this means is that you can specify or omit the arguments in any
order:
py::class_<A, PyA> c1(m, "A");
py::class_<B, PyB, std::shared_ptr<B>> c2(m, "B");
py::class_<C, std::shared_ptr<C>, PyB> c3(m, "C");
It also allows future class attributes (such as base types in the next
commit) to be passed as class template types rather than needing to use
a py::base<> wrapper.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 0efefaa..6d72ad7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -6,6 +6,7 @@
set(PYBIND11_TEST_FILES
test_buffers.cpp
test_callbacks.cpp
+ test_class_args.cpp
test_constants_and_functions.cpp
test_eigen.cpp
test_enum.cpp
diff --git a/tests/test_class_args.cpp b/tests/test_class_args.cpp
new file mode 100644
index 0000000..c120701
--- /dev/null
+++ b/tests/test_class_args.cpp
@@ -0,0 +1,69 @@
+/*
+ tests/test_class_args.cpp -- tests that various way of defining a class work
+
+ Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+ 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"
+
+PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
+
+template <int N> class BreaksBase {};
+template <int N> class BreaksTramp : public BreaksBase<N> {};
+// These should all compile just fine:
+typedef py::class_<BreaksBase<1>, std::unique_ptr<BreaksBase<1>>, BreaksTramp<1>> DoesntBreak1;
+typedef py::class_<BreaksBase<2>, BreaksTramp<2>, std::unique_ptr<BreaksBase<2>>> DoesntBreak2;
+typedef py::class_<BreaksBase<3>, std::unique_ptr<BreaksBase<3>>> DoesntBreak3;
+typedef py::class_<BreaksBase<4>, BreaksTramp<4>> DoesntBreak4;
+typedef py::class_<BreaksBase<5>> DoesntBreak5;
+typedef py::class_<BreaksBase<6>, std::shared_ptr<BreaksBase<6>>, BreaksTramp<6>> DoesntBreak6;
+typedef py::class_<BreaksBase<7>, BreaksTramp<7>, std::shared_ptr<BreaksBase<7>>> DoesntBreak7;
+typedef py::class_<BreaksBase<8>, std::shared_ptr<BreaksBase<8>>> DoesntBreak8;
+#define CHECK_BASE(N) static_assert(std::is_same<typename DoesntBreak##N::type, BreaksBase<N>>::value, \
+ "DoesntBreak" #N " has wrong type!")
+CHECK_BASE(1); CHECK_BASE(2); CHECK_BASE(3); CHECK_BASE(4); CHECK_BASE(5); CHECK_BASE(6); CHECK_BASE(7); CHECK_BASE(8);
+#define CHECK_ALIAS(N) static_assert(DoesntBreak##N::has_alias && std::is_same<typename DoesntBreak##N::type_alias, BreaksTramp<N>>::value, \
+ "DoesntBreak" #N " has wrong type_alias!")
+#define CHECK_NOALIAS(N) static_assert(!DoesntBreak##N::has_alias && std::is_void<typename DoesntBreak##N::type_alias>::value, \
+ "DoesntBreak" #N " has type alias, but shouldn't!")
+CHECK_ALIAS(1); CHECK_ALIAS(2); CHECK_NOALIAS(3); CHECK_ALIAS(4); CHECK_NOALIAS(5); CHECK_ALIAS(6); CHECK_ALIAS(7); CHECK_NOALIAS(8);
+#define CHECK_HOLDER(N, TYPE) static_assert(std::is_same<typename DoesntBreak##N::holder_type, std::TYPE##_ptr<BreaksBase<N>>>::value, \
+ "DoesntBreak" #N " has wrong holder_type!")
+CHECK_HOLDER(1, unique); CHECK_HOLDER(2, unique); CHECK_HOLDER(3, unique); CHECK_HOLDER(4, unique); CHECK_HOLDER(5, unique);
+CHECK_HOLDER(6, shared); CHECK_HOLDER(7, shared); CHECK_HOLDER(8, shared);
+
+// There's no nice way to test that these fail because they fail to compile; leave them here,
+// though, so that they can be manually tested by uncommenting them (and seeing that compilation
+// failures occurs).
+
+// We have to actually look into the type: the typedef alone isn't enough to instantiate the type:
+#define CHECK_BROKEN(N) static_assert(std::is_same<typename Breaks##N::type, BreaksBase<-N>>::value, \
+ "Breaks1 has wrong type!");
+
+//// Two holder classes:
+//typedef py::class_<BreaksBase<-1>, std::unique_ptr<BreaksBase<-1>>, std::unique_ptr<BreaksBase<-1>>> Breaks1;
+//CHECK_BROKEN(1);
+//// Two aliases:
+//typedef py::class_<BreaksBase<-2>, BreaksTramp<-2>, BreaksTramp<-2>> Breaks2;
+//CHECK_BROKEN(2);
+//// Holder + 2 aliases
+//typedef py::class_<BreaksBase<-3>, std::unique_ptr<BreaksBase<-3>>, BreaksTramp<-3>, BreaksTramp<-3>> Breaks3;
+//CHECK_BROKEN(3);
+//// Alias + 2 holders
+//typedef py::class_<BreaksBase<-4>, std::unique_ptr<BreaksBase<-4>>, BreaksTramp<-4>, std::shared_ptr<BreaksBase<-4>>> Breaks4;
+//CHECK_BROKEN(4);
+//// Invalid option (not a subclass or holder)
+//typedef py::class_<BreaksBase<-5>, BreaksTramp<-4>> Breaks5;
+//CHECK_BROKEN(5);
+//// Invalid option: multiple inheritance not supported:
+//template <> struct BreaksBase<-8> : BreaksBase<-6>, BreaksBase<-7> {};
+//typedef py::class_<BreaksBase<-8>, BreaksBase<-6>, BreaksBase<-7>> Breaks8;
+//CHECK_BROKEN(8);
+
+test_initializer class_args([](py::module &m) {
+ // Just test that this compiled okay
+ m.def("class_args_noop", []() {});
+});
diff --git a/tests/test_class_args.py b/tests/test_class_args.py
new file mode 100644
index 0000000..a1fc21a
--- /dev/null
+++ b/tests/test_class_args.py
@@ -0,0 +1,7 @@
+
+import pytest
+
+def test_class_args():
+ # There's basically nothing to test here; just make sure the code compiled and declared its definition
+ from pybind11_tests import class_args_noop
+ class_args_noop()
diff --git a/tests/test_issues.cpp b/tests/test_issues.cpp
index 16a44c7..0a5a0b0 100644
--- a/tests/test_issues.cpp
+++ b/tests/test_issues.cpp
@@ -51,7 +51,7 @@
}
};
- py::class_<Base, std::unique_ptr<Base>, DispatchIssue>(m2, "DispatchIssue")
+ py::class_<Base, DispatchIssue>(m2, "DispatchIssue")
.def(py::init<>())
.def("dispatch", &Base::dispatch);
diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp
index e591ba9..ac5b3fb 100644
--- a/tests/test_virtual_functions.cpp
+++ b/tests/test_virtual_functions.cpp
@@ -253,31 +253,31 @@
void initialize_inherited_virtuals(py::module &m) {
// Method 1: repeat
- py::class_<A_Repeat, std::unique_ptr<A_Repeat>, PyA_Repeat>(m, "A_Repeat")
+ py::class_<A_Repeat, PyA_Repeat>(m, "A_Repeat")
.def(py::init<>())
.def("unlucky_number", &A_Repeat::unlucky_number)
.def("say_something", &A_Repeat::say_something)
.def("say_everything", &A_Repeat::say_everything);
- py::class_<B_Repeat, std::unique_ptr<B_Repeat>, PyB_Repeat>(m, "B_Repeat", py::base<A_Repeat>())
+ py::class_<B_Repeat, PyB_Repeat>(m, "B_Repeat", py::base<A_Repeat>())
.def(py::init<>())
.def("lucky_number", &B_Repeat::lucky_number);
- py::class_<C_Repeat, std::unique_ptr<C_Repeat>, PyC_Repeat>(m, "C_Repeat", py::base<B_Repeat>())
+ py::class_<C_Repeat, PyC_Repeat>(m, "C_Repeat", py::base<B_Repeat>())
.def(py::init<>());
- py::class_<D_Repeat, std::unique_ptr<D_Repeat>, PyD_Repeat>(m, "D_Repeat", py::base<C_Repeat>())
+ py::class_<D_Repeat, PyD_Repeat>(m, "D_Repeat", py::base<C_Repeat>())
.def(py::init<>());
// Method 2: Templated trampolines
- py::class_<A_Tpl, std::unique_ptr<A_Tpl>, PyA_Tpl<>>(m, "A_Tpl")
+ py::class_<A_Tpl, PyA_Tpl<>>(m, "A_Tpl")
.def(py::init<>())
.def("unlucky_number", &A_Tpl::unlucky_number)
.def("say_something", &A_Tpl::say_something)
.def("say_everything", &A_Tpl::say_everything);
- py::class_<B_Tpl, std::unique_ptr<B_Tpl>, PyB_Tpl<>>(m, "B_Tpl", py::base<A_Tpl>())
+ py::class_<B_Tpl, PyB_Tpl<>>(m, "B_Tpl", py::base<A_Tpl>())
.def(py::init<>())
.def("lucky_number", &B_Tpl::lucky_number);
- py::class_<C_Tpl, std::unique_ptr<C_Tpl>, PyB_Tpl<C_Tpl>>(m, "C_Tpl", py::base<B_Tpl>())
+ py::class_<C_Tpl, PyB_Tpl<C_Tpl>>(m, "C_Tpl", py::base<B_Tpl>())
.def(py::init<>());
- py::class_<D_Tpl, std::unique_ptr<D_Tpl>, PyB_Tpl<D_Tpl>>(m, "D_Tpl", py::base<C_Tpl>())
+ py::class_<D_Tpl, PyB_Tpl<D_Tpl>>(m, "D_Tpl", py::base<C_Tpl>())
.def(py::init<>());
};
@@ -287,7 +287,7 @@
/* Important: indicate the trampoline class PyExampleVirt using the third
argument to py::class_. The second argument with the unique pointer
is simply the default holder type used by pybind11. */
- py::class_<ExampleVirt, std::unique_ptr<ExampleVirt>, PyExampleVirt>(m, "ExampleVirt")
+ py::class_<ExampleVirt, PyExampleVirt>(m, "ExampleVirt")
.def(py::init<int>())
/* Reference original class in function definitions */
.def("run", &ExampleVirt::run)
@@ -301,7 +301,7 @@
.def(py::init<int, int>());
#if !defined(__INTEL_COMPILER)
- py::class_<NCVirt, std::unique_ptr<NCVirt>, NCVirtTrampoline>(m, "NCVirt")
+ py::class_<NCVirt, NCVirtTrampoline>(m, "NCVirt")
.def(py::init<>())
.def("get_noncopyable", &NCVirt::get_noncopyable)
.def("get_movable", &NCVirt::get_movable)