Skip to content

Commit 63534a8

Browse files
Python: Allow enabling const_cast for returned const references.
1 parent 2978653 commit 63534a8

4 files changed

Lines changed: 12 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Feature | C | C# | Python | Comments
7373
Templates | ✅ | ✅ | ✅ | We don't use the templating mechanism in any of the languages, instead the template arguments are baked into the names. Also read about [Adding specific template specializations](/docs/adding_template_specializations.md).
7474
Customizing type names | ✅ | ✅ | ⚠️ | In Python, doesn't work in template arguments. Read about [Customizing type names](/docs/customizing_type_names.md).
7575
Function overloading | ✅ | ✅ | ✅ | In C, functions are automatically renamed to disambiguate them. Sometimes this is done in other languages too.
76-
Const-correctness | ✅ | ✅ | ⚠️ | In C#, each class is emitted in two halves, one for const methods and another for non-const, with the latter inheriting from the former. This allows emulating C++ const references in C#.<br/>In Python, we eventually want to use the same solution as in C#, but it's not yet implemented, so when you have a function returning `const T &`, we copy the result, because otherwise the default Pybind behavior would be to `const_cast<T &>(...)` it, which lets the user modify const C++ objects.
76+
Const-correctness | ✅ | ✅ | ⚠️ | In C#, each class is emitted in two halves, one for const methods and another for non-const, with the latter inheriting from the former. This allows emulating C++ const references in C#.<br/>In Python, Pybind doesn't support const references properly, so when you have a function returning `const T &`, we copy the result, because otherwise the default Pybind behavior would be to `const_cast<T &>(...)` it, which lets the user modify const C++ objects (define `-DMB_PB11_CONST_CAST_RETURNED_CONST_REFS` to switch to that behavior). Eventually we want to replace this with the same system we're using for C#.
7777
Inheritance (including multiple inheritance) | ✅ | ✅ | ✅ | In C, we generate upcast/downcast functions, and can optionally copy members from bases to derived classes, to allow accessing them without the upcasts.<br/>In C#, we don't use C# inheritance, because there's no multiple inheritance support, and because the single inheritance is already spent on implementing const-correctness (see above). Instead we emit unrelated classes with upcast/downcast conversion operators between them, and copy the members from bases to derived classes to imitate inheritance.
7878
Aggregate initialization | ✅ | ✅ | ✅ | Aggregates (structs/classes with no custom constructor, initializable in C++ with a list of their members) get member-wise constructors.
7979
Overloaded operators | ✅ | ✅ | ✅ | In C, they become functions. In C# and Python, they become operators if possible, falling back to functions.

docs/generating_python.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ There are a few additional macros that you can define to tune the bindings:
137137

138138
If you want to import another Python module at startup as a dependency, pass its name to this macro. It acceps a list of quoted module names. `'...'` here is the shell's quoting and not a part of the syntax.
139139

140+
* **`-DMB_PB11_CONST_CAST_RETURNED_CONST_REFS`** — When a const reference is returned from a function, `const_cast` it into a non-const reference instead of copying the result.
141+
142+
This is faster than copying, but is allows the user to modify C++ objects they're not supposed to.
143+
140144
* **Adding aliases** — Python lets you add aliases for things like functions, types, and even class members, simply using what looks like variable assignment.
141145

142146
E.g. given `struct Vec3 {float x, y, z;};`, which binds to `mylib.Vec3` in Python, you could do `mylib.Vec3.foo = mylib.Vec3.x`, and then `foo` would be usable as an alternative name for `x` in every instance of the class.

docs/state_of_the_project.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ The Python support works, but could use a rewrite, probably with [Nanobind](http
2828

2929
* Having to rebuild the modules for each minor Python version. We have [a Pybind fork](https://github.com/MeshInspector/mrbind-pybind11) that improves the situation somewhat, but it's not ideal.
3030

31-
* Const-correctness beind implemented crudely. Returning a const reference from a function makes a copy of the object to prevent Pybind from `const_cast`ing it into a mutable reference. Ideally we would replace it with the approach we currently use in C# (each class being split into a const half, and a mutable half inheriting from the const one).
31+
* Const-correctness beind implemented crudely. Returning a const reference from a function makes a copy of the object to prevent Pybind from `const_cast`ing it into a mutable reference. You can define `-DMB_PB11_CONST_CAST_RETURNED_CONST_REFS` to `const_cast` instead.
32+
33+
Ideally we would replace it with the approach we currently use in C# (each class being split into a const half, and a mutable half inheriting from the const one).
3234

3335
* Some non-trivial initialization happening at module load. This doesn't cause slowdown in practice even in [a large library](https://meshlib.io/) MRBind was developer for, but is still not ideal.
3436

include/mrbind/targets/pybind11/core.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,10 +1526,12 @@ namespace MRBind::pb11
15261526
static constexpr pybind11::return_value_policy ret_policy =
15271527
returns_unique_ptr_to_builtin ?
15281528
pybind11::return_value_policy::take_ownership :
1529-
(std::is_pointer_v<LambdaReturnTypeAdjustedWrapped> || std::is_reference_v<LambdaReturnTypeAdjustedWrapped>) &&
1529+
(std::is_pointer_v<LambdaReturnTypeAdjustedWrapped> || std::is_reference_v<LambdaReturnTypeAdjustedWrapped>)
15301530
// This is important. If we return a const reference to a copyable type, we actually COPY it.
15311531
// Because otherwise pybind11 casts away constness and propagates changes through that reference!
1532-
!std::is_const_v<LambdaReturnTypeAdjustedWrapperPtrRefStripped>
1532+
#if !MB_PB11_CONST_CAST_RETURNED_CONST_REFS // Setting this to `1` `const_cast`s the result instead.
1533+
&& !std::is_const_v<LambdaReturnTypeAdjustedWrapperPtrRefStripped>
1534+
#endif
15331535
? bool(Kind & FuncKind::member_nonstatic) ? pybind11::return_value_policy::reference_internal : pybind11::return_value_policy::reference :
15341536
// This is important too, otherwise pybind11 will const_cast and then move!
15351537
std::is_const_v<LambdaReturnTypeAdjustedWrapperPtrRefStripped> ? (std::is_copy_constructible_v<LambdaReturnTypeAdjustedWrapperPtrRefStripped> ? pybind11::return_value_policy::copy : pybind11::return_value_policy::reference/*ugh*/)

0 commit comments

Comments
 (0)