-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for implicit conversions to C++ types
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
Showing
16 changed files
with
819 additions
and
274 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ set(PYBIND11_EXAMPLES | |
example17.cpp | ||
example18.cpp | ||
example19.cpp | ||
example20.cpp | ||
issues.cpp | ||
) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.