Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions docs/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,18 @@ The shorthand notation is also available for default arguments:
Exporting variables
===================

To expose a value from C++, use the ``attr`` function to register it in a module
as shown below. Built-in types and general objects (more on that later) can be
To expose a value from C++, use the ``attr`` function to register it in a
module as shown below. Built-in types and general objects (more on that later)
are automatically converted when assigned as attributes, and can be explicitly
converted using the function ``py::cast``.

.. code-block:: cpp

PYBIND11_PLUGIN(example) {
py::module m("example", "pybind11 example plugin");
m.attr("the_answer") = py::cast(42);
m.attr("what") = py::cast("World");
m.attr("the_answer") = 42;
py::object world = py::cast("World");
m.attr("what") = world;
return m.ptr();
}

Expand Down
4 changes: 4 additions & 0 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,10 @@ template <> inline void object::cast() && { return; }

NAMESPACE_BEGIN(detail)

// Declared in pytypes.h:
template <typename T, enable_if_t<!is_pyobject<T>::value, int>>
object object_or_cast(T &&o) { return pybind11::cast(std::forward<T>(o)); }

struct overload_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro
template <typename ret_type> using overload_caster_t = conditional_t<
cast_is_temporary_value_reference<ret_type>::value, make_caster<ret_type>, overload_unused>;
Expand Down
37 changes: 29 additions & 8 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ using tuple_accessor = accessor<accessor_policies::tuple_item>;

/// Tag and check to identify a class which implements the Python object API
class pyobject_tag { };
template <typename T> using is_pyobject = std::is_base_of<pyobject_tag, T>;
template <typename T> using is_pyobject = std::is_base_of<pyobject_tag, typename std::remove_reference<T>::type>;

/// Mixin which adds common functions to handle, object and various accessors.
/// The only requirement for `Derived` is to implement `PyObject *Derived::ptr() const`.
Expand Down Expand Up @@ -81,7 +81,7 @@ NAMESPACE_END(detail)
class handle : public detail::object_api<handle> {
public:
handle() = default;
handle(PyObject *ptr) : m_ptr(ptr) { }
handle(PyObject *ptr) : m_ptr(ptr) { } // Allow implicit conversion from PyObject*

PyObject *ptr() const { return m_ptr; }
PyObject *&ptr() { return m_ptr; }
Expand Down Expand Up @@ -224,19 +224,36 @@ inline handle get_function(handle value) {
return value;
}

// Helper aliases/functions to support implicit casting of values given to python accessors/methods.
// When given a pyobject, this simply returns the pyobject as-is; for other C++ type, the value goes
// through pybind11::cast(obj) to convert it to an `object`.
template <typename T, enable_if_t<is_pyobject<T>::value, int> = 0>
auto object_or_cast(T &&o) -> decltype(std::forward<T>(o)) { return std::forward<T>(o); }
// The following casting version is implemented in cast.h:
template <typename T, enable_if_t<!is_pyobject<T>::value, int> = 0>
object object_or_cast(T &&o);
// Match a PyObject*, which we want to convert directly to handle via its converting constructor
inline handle object_or_cast(PyObject *ptr) { return ptr; }


template <typename Policy>
class accessor : public object_api<accessor<Policy>> {
using key_type = typename Policy::key_type;

public:
accessor(handle obj, key_type key) : obj(obj), key(std::move(key)) { }

// accessor overload required to override default assignment operator (templates are not allowed
// to replace default compiler-generated assignments).
void operator=(const accessor &a) && { std::move(*this).operator=(handle(a)); }
void operator=(const accessor &a) & { operator=(handle(a)); }
void operator=(const object &o) && { std::move(*this).operator=(handle(o)); }
void operator=(const object &o) & { operator=(handle(o)); }
void operator=(handle value) && { Policy::set(obj, key, value); }
void operator=(handle value) & { get_cache() = reinterpret_borrow<object>(value); }

template <typename T> void operator=(T &&value) && {
Policy::set(obj, key, object_or_cast(std::forward<T>(value)));
}
template <typename T> void operator=(T &&value) & {
get_cache() = reinterpret_borrow<object>(object_or_cast(std::forward<T>(value)));
}

template <typename T = Policy>
PYBIND11_DEPRECATED("Use of obj.attr(...) as bool is deprecated in favor of pybind11::hasattr(obj, ...)")
Expand Down Expand Up @@ -773,7 +790,9 @@ class list : public object {
}
size_t size() const { return (size_t) PyList_Size(m_ptr); }
detail::list_accessor operator[](size_t index) const { return {*this, index}; }
void append(handle h) const { PyList_Append(m_ptr, h.ptr()); }
template <typename T> void append(T &&val) const {
PyList_Append(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr());
}
};

class args : public tuple { PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check) };
Expand All @@ -786,7 +805,9 @@ class set : public object {
if (!m_ptr) pybind11_fail("Could not allocate set object!");
}
size_t size() const { return (size_t) PySet_Size(m_ptr); }
bool add(const object &object) const { return PySet_Add(m_ptr, object.ptr()) == 0; }
template <typename T> bool add(T &&val) const {
return PySet_Add(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 0;
}
void clear() const { PySet_Clear(m_ptr); }
};

Expand Down
2 changes: 1 addition & 1 deletion tests/pybind11_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ PYBIND11_PLUGIN(pybind11_tests) {
for (const auto &initializer : initializers())
initializer(m);

if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = py::cast(false);
if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = false;

return m.ptr();
}
2 changes: 1 addition & 1 deletion tests/test_eigen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ test_initializer eigen([](py::module &m) {
typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR;
typedef Eigen::SparseMatrix<float> SparseMatrixC;

m.attr("have_eigen") = py::cast(true);
m.attr("have_eigen") = true;

// Non-symmetric matrix with zero elements
Eigen::MatrixXf mat(5, 6);
Expand Down
2 changes: 1 addition & 1 deletion tests/test_exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void throws_logic_error() {

struct PythonCallInDestructor {
PythonCallInDestructor(const py::dict &d) : d(d) {}
~PythonCallInDestructor() { d["good"] = py::cast(true); }
~PythonCallInDestructor() { d["good"] = true; }

py::dict d;
};
Expand Down
5 changes: 5 additions & 0 deletions tests/test_issues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,11 @@ void init_issues(py::module &m) {
.def_static("make", &MyDerived::make)
.def_static("make2", &MyDerived::make);

py::dict d;
std::string bar = "bar";
d["str"] = bar;
d["num"] = 3.7;

/// Issue #528: templated constructor
m2.def("tpl_constr_vector", [](std::vector<TplConstrClass> &) {});
m2.def("tpl_constr_map", [](std::unordered_map<TplConstrClass, TplConstrClass> &) {});
Expand Down
58 changes: 48 additions & 10 deletions tests/test_python_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class ExamplePythonTypes {
py::set get_set() {
py::set set;
set.add(py::str("key1"));
set.add(py::str("key2"));
set.add("key2");
set.add(std::string("key3"));
return set;
}

Expand All @@ -59,7 +60,7 @@ class ExamplePythonTypes {
/* Create, manipulate, and return a Python list */
py::list get_list() {
py::list list;
list.append(py::str("value"));
list.append("value");
py::print("Entry at position 0:", list[0]);
list[0] = py::str("overwritten");
return list;
Expand Down Expand Up @@ -269,7 +270,7 @@ test_initializer python_types([](py::module &m) {
d["missing_attr_chain"] = "raised"_s;
}

d["is_none"] = py::cast(o.attr("basic_attr").is_none());
d["is_none"] = o.attr("basic_attr").is_none();

d["operator()"] = o.attr("func")(1);
d["operator*"] = o.attr("func")(*o.attr("begin_end"));
Expand All @@ -279,13 +280,13 @@ test_initializer python_types([](py::module &m) {

m.def("test_tuple_accessor", [](py::tuple existing_t) {
try {
existing_t[0] = py::cast(1);
existing_t[0] = 1;
} catch (const py::error_already_set &) {
// --> Python system error
// Only new tuples (refcount == 1) are mutable
auto new_t = py::tuple(3);
for (size_t i = 0; i < new_t.size(); ++i) {
new_t[i] = py::cast(i);
new_t[i] = i;
}
return new_t;
}
Expand All @@ -294,15 +295,15 @@ test_initializer python_types([](py::module &m) {

m.def("test_accessor_assignment", []() {
auto l = py::list(1);
l[0] = py::cast(0);
l[0] = 0;

auto d = py::dict();
d["get"] = l[0];
auto var = l[0];
d["deferred_get"] = var;
l[0] = py::cast(1);
l[0] = 1;
d["set"] = l[0];
var = py::cast(99); // this assignment should not overwrite l[0]
var = 99; // this assignment should not overwrite l[0]
d["deferred_set"] = l[0];
d["var"] = var;

Expand Down Expand Up @@ -338,8 +339,8 @@ test_initializer python_types([](py::module &m) {
}, py::arg_v("x", std::experimental::nullopt, "None"));
#endif

m.attr("has_optional") = py::cast(has_optional);
m.attr("has_exp_optional") = py::cast(has_exp_optional);
m.attr("has_optional") = has_optional;
m.attr("has_exp_optional") = has_exp_optional;

m.def("test_default_constructors", []() {
return py::dict(
Expand Down Expand Up @@ -389,4 +390,41 @@ test_initializer python_types([](py::module &m) {
py::class_<MoveOutContainer>(m, "MoveOutContainer")
.def(py::init<>())
.def_property_readonly("move_list", &MoveOutContainer::move_list);

m.def("get_implicit_casting", []() {
py::dict d;
d["char*_i1"] = "abc";
const char *c2 = "abc";
d["char*_i2"] = c2;
d["char*_e"] = py::cast(c2);
d["char*_p"] = py::str(c2);

d["int_i1"] = 42;
int i = 42;
d["int_i2"] = i;
i++;
d["int_e"] = py::cast(i);
i++;
d["int_p"] = py::int_(i);

d["str_i1"] = std::string("str");
std::string s2("str1");
d["str_i2"] = s2;
s2[3] = '2';
d["str_e"] = py::cast(s2);
s2[3] = '3';
d["str_p"] = py::str(s2);

py::list l(2);
l[0] = 3;
l[1] = py::cast(6);
l.append(9);
l.append(py::cast(12));
l.append(py::int_(15));

return py::dict(
"d"_a=d,
"l"_a=l
);
});
});
16 changes: 15 additions & 1 deletion tests/test_python_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ def test_instance(capture):
"""
with capture:
set_result = instance.get_set()
set_result.add('key3')
set_result.add('key4')
instance.print_set(set_result)
assert capture.unordered == """
key: key1
key: key2
key: key3
key: key4
"""
with capture:
set_result = instance.get_set2()
Expand Down Expand Up @@ -386,3 +387,16 @@ def test_move_out_container():
c = MoveOutContainer()
moved_out_list = c.move_list
assert [x.value for x in moved_out_list] == [0, 1, 2]


def test_implicit_casting():
"""Tests implicit casting when assigning or appending to dicts and lists."""
from pybind11_tests import get_implicit_casting

z = get_implicit_casting()
assert z['d'] == {
'char*_i1': 'abc', 'char*_i2': 'abc', 'char*_e': 'abc', 'char*_p': 'abc',
'str_i1': 'str', 'str_i2': 'str1', 'str_e': 'str2', 'str_p': 'str3',
'int_i1': 42, 'int_i2': 42, 'int_e': 43, 'int_p': 44
}
assert z['l'] == [3, 6, 9, 12, 15]