Skip to content

Commit

Permalink
Add support for implicit conversions to C++ types
Browse files Browse the repository at this point in the history
See issue #259.

This commit adds support for py::implicitly_convertible<From,To>() to
perform implicit conversion, at the C++ level, when To is not a
pybind11-registered type (e.g. a custom C++ type that should not be
exposed, or a primitive type (double, etc.)).

In essence, this lets you make use of C++ implicit converters (typically
via a 'From::operator To()' or a non-explicit 'To::To(const From&)'
constructor) with pybind11 instances without needing to create an
explicit pybind11 interface for the conversion.

As a simple example, consider the two classes:

    class A {
        ...
        void method(uint64_t val) { ... }
    }
    class B {
        ...
        operator uint64_t() { ... } // returns unique identifier
    }

In C++, you can call `a.method(22)` or `a.method(b)`.  Telling
pybind11 about the conversion allows you to do the same in python.

Without the implicit conversion, you would have to either overload
A.method to accept both uint64_t and B instances, or would have to add
a method to B's interface that returns the id, then pass this method
result to A's method.
  • Loading branch information
jagerman committed Jul 18, 2016
1 parent 1f66a58 commit f96f23b
Show file tree
Hide file tree
Showing 16 changed files with 819 additions and 274 deletions.
49 changes: 45 additions & 4 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,8 @@ Implicit type conversions

Suppose that instances of two types ``A`` and ``B`` are used in a project, and
that an ``A`` can easily be converted into an instance of type ``B`` (examples of this
could be a fixed and an arbitrary precision number type).
could be a fixed and an arbitrary precision number type) through a ``B`` constructor
that accepts an ``A`` instance:

.. code-block:: cpp
Expand Down Expand Up @@ -628,10 +629,50 @@ Python side:
py::implicitly_convertible<A, B>();
.. note::
With this statement, our Python code can now call ``func(a)`` and have it
treated as if we had written ``func(B(a))``.

pybind11 also supports converting from pybind11-registered types to
non-registered types using implicit conversion at the C++ level. If ``A`` is a
pybind11-registered type but ``B`` is not, the registration above tells
pybind11 that it is able to perform implicit conversion from an ``a`` variable
containing an ``A`` instance to the ``B`` C++ type using C++ implicit
conversion. This allows you to make use of C++ implicit conversions, as in
this example:

.. code-block:: cpp
class A1 {
// ...
operator double () const { return 42.0; }
};
class A2 { /* ... */ }
class PrivateType {
PrivateType(const A2 &a2) { /* ... */ }
// ...
};
py::class_<A1>(m, "A1")
/// ... members ...
py::class_<A2>(m, "A2")
/// ... members ...
// Note: no py::class_<PrivateType>
py::implicitly_convertible<A1, double>();
py::implicitly_convertible<A2, PrivateType>();
m.def("square", [](double v) { return v*v; });
m.def("special", [](const PrivateType &p) { /* ... */ });
With this registration in place, our python code can now call ``square(a)`` and
``special(b)``; pybind11 will use C++ implicit conversion on ``a`` and ``b`` to
call the functions with a ``double`` and a ``PrivateType`` (where PrivateType
is not exposed via pybind11).

.. seealso::

Implicit conversions from ``A`` to ``B`` only work when ``B`` is a custom
data type that is exposed to Python via pybind11.
The file :file:`example/example20.cpp` contains a complete example that
demonstrates how to use both types of implicit conversions in more detail.

.. _static_properties:

Expand Down
1 change: 1 addition & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ set(PYBIND11_EXAMPLES
example17.cpp
example18.cpp
example19.cpp
example20.cpp
issues.cpp
)

Expand Down
2 changes: 2 additions & 0 deletions example/example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void init_ex16(py::module &);
void init_ex17(py::module &);
void init_ex18(py::module &);
void init_ex19(py::module &);
void init_ex20(py::module &);
void init_issues(py::module &);

#if defined(PYBIND11_TEST_EIGEN)
Expand Down Expand Up @@ -56,6 +57,7 @@ PYBIND11_PLUGIN(example) {
init_ex17(m);
init_ex18(m);
init_ex19(m);
init_ex20(m);
init_issues(m);

#if defined(PYBIND11_TEST_EIGEN)
Expand Down
174 changes: 174 additions & 0 deletions example/example20.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
example/example20.cpp -- implicit conversion between types
Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/

#include "example.h"
#include <cmath>

/// Objects to test implicit conversion
class Ex20_A {
public:
// Implicit conversion *from* double
Ex20_A(double v) : value{v} {}
// Default constructor
Ex20_A() : Ex20_A(42.0) {}
// Implicit conversion *to* double
virtual operator double() const { std::cout << "Ex20_A double conversion operator" << std::endl; return value; }
private:
double value;
};
class Ex20_E;
class Ex20_B : public Ex20_A {
public:
// Implicit conversion to Ex20_E
operator Ex20_E() const;
};
class Ex20_C : public Ex20_B {
public:
// Implicit conversion to double
virtual operator double() const override { return 3.14159265358979323846; }
// Implicit conversion to string
operator std::string() const { return "pi"; }
};
class Ex20_D : public Ex20_A {
public:
// Implicit conversion to double
virtual operator double() const override { return 2.71828182845904523536; }
// Implicit conversion to string
operator std::string() const { return "e"; }
};
// This class won't be registered with pybind11, but a function accepting it will be--the function
// can only be called with arguments that are implicitly convertible to Ex20_E
class Ex20_E {
public:
Ex20_E() = delete;
Ex20_E(const Ex20_E &e) : value{e.value} { std::cout << "Ex20_E @ " << this << " copy constructor" << std::endl; }
Ex20_E(Ex20_E &&e) : value{std::move(e.value)} { std::cout << "Ex20_E @ " << this << " move constructor" << std::endl; }
~Ex20_E() { std::cout << "Ex20_E @ " << this << " destructor" << std::endl; }
// explicit constructors should not be called by implicit conversion:
explicit Ex20_E(double d) : value{d} { std::cout << "Ex20_E @ " << this << " double constructor" << std::endl; }
explicit Ex20_E(const Ex20_A &a) : value{(double)a / 3.0} { std::cout << "Ex20_E @ " << this << " explicit Ex20_A constructor" << std::endl; }
// Convertible implicitly from D:
Ex20_E(const Ex20_D &d) : value{3*d} { std::cout << "Ex20_E @ " << this << " implicit Ex20_D constructor" << std::endl; }
// Implicit conversion to double:
operator double() const { std::cout << "Ex20_E double conversion operator" << std::endl; return value; }
private:
double value;
};
Ex20_B::operator Ex20_E() const { std::cout << "Ex20_B @ " << this << " Ex20_E conversion operator" << std::endl; return Ex20_E(2*(double)(*this)); }
// Class without a move constructor (just to be sure we don't depend on a move constructor).
// Unlike the above, we *will* expose this one to python, but will declare its
// implicitly_convertible before registering it, which will result in C++ (not python) type
// conversion.
class Ex20_F {
public:
Ex20_F() : value{99.0} { std::cout << "Ex20_F @ " << this << " default constructor" << std::endl; }
Ex20_F(const Ex20_A &a) : value{(double)a*1000} { std::cout << "Ex20_F @ " << this << " Ex20_A conversion constructor" << std::endl; }
Ex20_F(const Ex20_F &f) : value{f.value} { std::cout << "Ex20_F @ " << this << " copy constructor" << std::endl; }
~Ex20_F() { std::cout << "Ex20_F @ " << this << " destructor" << std::endl; }
operator double() const { return value; }
private:
double value;
};

class Ex20_G1 {
public:
operator long() const { return 111; }
};
class Ex20_G2 : public Ex20_G1 {
public:
operator long() const { return 222; }
};
class Ex20_G3 {
public:
operator long() const { return 333; }
};
class Ex20_G4 : public Ex20_G3 {
public:
operator long() const { return 444; }
};

void print_double(double d) { std::cout << d << std::endl; }
void print_long(long l) { std::cout << l << std::endl; }
void print_string(const std::string &s) { std::cout << s << std::endl; }
void print_ex20e(const Ex20_E &e) { std::cout << (double) e << std::endl; }
void print_ex20f(const Ex20_F &f) { std::cout << (double) f << std::endl; }

void init_ex20(py::module &m) {

py::class_<Ex20_A> a(m, "Ex20_A");
a.def(py::init<>());
a.def(py::init<double>());

// We can construct a Ex20_A from a double:
py::implicitly_convertible<py::float_, Ex20_A>();

// It can also be implicitly to a double:
py::implicitly_convertible<Ex20_A, double>();


py::class_<Ex20_B> b(m, "Ex20_B", a);
b.def(py::init<>());

py::class_<Ex20_C> c(m, "Ex20_C", b);
c.def(py::init<>());

py::class_<Ex20_D> d(m, "Ex20_D", a);
d.def(py::init<>());

// NB: don't need to implicitly declare Ex20_{B,C} as convertible to double: they automatically
// get that since we told pybind11 they inherit from A
py::implicitly_convertible<Ex20_C, std::string>();
py::implicitly_convertible<Ex20_D, std::string>();

// NB: Ex20_E is a non-pybind-registered class:
//
// This should fail: Ex20_A is *not* C++ implicitly convertible to Ex20_E (the constructor is
// marked explicit):
try {
py::implicitly_convertible<Ex20_A, Ex20_E>();
std::cout << "py::implicitly_convertible<Ex20_A, Ex20_E>() should have thrown, but didn't!" << std::endl;
}
catch (std::runtime_error) {}

py::implicitly_convertible<Ex20_B, Ex20_E>();
// This isn't needed, since pybind knows C inherits from B
//py::implicitly_convertible<Ex20_C, Ex20_E>();
py::implicitly_convertible<Ex20_D, Ex20_E>();

m.def("print_double", &print_double);
m.def("print_long", &print_long);
m.def("print_string", &print_string);
m.def("print_ex20e", &print_ex20e);
m.def("print_ex20f", &print_ex20f);

// Here's how we can get C++-level implicit conversion even with a pybind-registered type: tell
// pybind11 that the type is convertible to F before registering F:
py::implicitly_convertible<Ex20_A, Ex20_F>();

py::class_<Ex20_F> f(m, "Ex20_F");
// We allow Ex20_F to be constructed in Python, but don't provide a conversion constructor from
// Ex20_A. C++ has an implicit one, however, that we told pybind11 about above. In practice
// this means we are allowed to pass Ex20_A instances to functions taking Ex20_F arguments, but
// aren't allowed to write `ex20_func(Ex20_F(a))` because the explicit conversion is
// (intentionally) not exposed to python. (Whether this is useful is really up to the
// developer).
f.def(py::init<>());

py::class_<Ex20_G1> g1(m, "Ex20_G1"); g1.def(py::init<>());
py::class_<Ex20_G2> g2(m, "Ex20_G2", g1); g2.def(py::init<>());
py::class_<Ex20_G3> g3(m, "Ex20_G3"); g3.def(py::init<>());
py::class_<Ex20_G4> g4(m, "Ex20_G4", g3); g4.def(py::init<>());
// Make sure that the order we declare convertibility doesn't matter: i.e. the base class
// conversions here (G1 and G3) should not be invoked for G2 and G4, regardless of the
// implicitly convertible declaration order.
py::implicitly_convertible<Ex20_G2, long>();
py::implicitly_convertible<Ex20_G1, long>();
py::implicitly_convertible<Ex20_G3, long>();
py::implicitly_convertible<Ex20_G4, long>();
}
54 changes: 54 additions & 0 deletions example/example20.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python
from __future__ import print_function
import sys
sys.path.append('.')

from example import Ex20_A
from example import Ex20_B
from example import Ex20_C
from example import Ex20_D
from example import Ex20_F
from example import Ex20_G1
from example import Ex20_G2
from example import Ex20_G3
from example import Ex20_G4
from example import print_double
from example import print_long
from example import print_string
from example import print_ex20e
from example import print_ex20f

# Ex20_A is declared cpp convertible to double; Ex20_B is a registered subclass of Ex20_A,
# and Ex20_C is a registered subclass of Ex20_B. All should be convertible to double
# through Ex20_A's base class convertibility.
print_double(Ex20_A()) # 42
print_double(Ex20_A((5 ** (1/2.0) + 1) / 2)) # Phi = 1.6180339...
print_double(Ex20_B()) # 42 (via Ex20_A's conversion operator)
print_double(Ex20_C()) # pi (overridden from A's double conv op)
print_string(Ex20_C()) # the string "pi"
print_double(Ex20_D()) # e (overridden from A's double conv op)
print_string(Ex20_D()) # "e"

try:
print_ex20e(Ex20_A())
print("BAD: Ex20_A should not be implicitly convertible to Ex20_E")
except TypeError:
pass

print_ex20e(Ex20_B()) # 84
print_ex20e(Ex20_C()) # 6.28319 (2*pi)
print_ex20e(Ex20_D()) # 8.15485 (3*e)

print_ex20f(Ex20_F()) # 99
print_ex20f(Ex20_A(0.25)) # 250, via C++ implicit conversion
print_ex20f(Ex20_C()) # 1000pi = 3141.59, via C++ implicit conversion
try:
print_ex20f(Ex20_F(Ex20_A(4)))
print("BAD: Ex20_F conversion constructor (from Ex20_A) should not have been exposed to python")
except TypeError:
pass

print_long(Ex20_G1())
print_long(Ex20_G2())
print_long(Ex20_G3())
print_long(Ex20_G4())
49 changes: 49 additions & 0 deletions example/example20.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Ex20_A double conversion operator
42
Ex20_A double conversion operator
1.61803
Ex20_A double conversion operator
42
3.14159
pi
2.71828
e
Ex20_B @ 0x160a1a0 Ex20_E conversion operator
Ex20_A double conversion operator
Ex20_E @ 0x7fff05c4f688 double constructor
Ex20_E @ 0x160b400 move constructor
Ex20_E @ 0x7fff05c4f688 destructor
Ex20_E double conversion operator
84
Ex20_E @ 0x160b400 destructor
Ex20_B @ 0x1609270 Ex20_E conversion operator
Ex20_E @ 0x7fff05c4f688 double constructor
Ex20_E @ 0x160b400 move constructor
Ex20_E @ 0x7fff05c4f688 destructor
Ex20_E double conversion operator
6.28319
Ex20_E @ 0x160b400 destructor
Ex20_E @ 0x7fff05c4f688 implicit Ex20_D constructor
Ex20_E @ 0x160b400 move constructor
Ex20_E @ 0x7fff05c4f688 destructor
Ex20_E double conversion operator
8.15485
Ex20_E @ 0x160b400 destructor
Ex20_F @ 0x1609270 default constructor
99
Ex20_F @ 0x1609270 destructor
Ex20_A double conversion operator
Ex20_F @ 0x7fff05c4f668 Ex20_A conversion constructor
Ex20_F @ 0x160b400 copy constructor
Ex20_F @ 0x7fff05c4f668 destructor
250
Ex20_F @ 0x160b400 destructor
Ex20_F @ 0x7fff05c4f668 Ex20_A conversion constructor
Ex20_F @ 0x160b400 copy constructor
Ex20_F @ 0x7fff05c4f668 destructor
3141.59
Ex20_F @ 0x160b400 destructor
111
222
333
444

0 comments on commit f96f23b

Please sign in to comment.