Skip to content

Commit 78913c5

Browse files
authored
Merge pull request #382 from ruby-rice/dev
Dev
2 parents c9b6cb7 + 2c92ddb commit 78913c5

20 files changed

Lines changed: 374 additions & 240 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Incompatible Changes:
4141

4242
Think of this as similar to how you would capture a block in Ruby using the &block syntax.
4343

44+
* The `Data_Type<T>::define()` method has been removed. See the [Class Templates](docs/bindings/class_templates.md) documentation for the recommended approach.
45+
4446
## 4.9.1 (2026-01-04)
4547
This release focuses on improving memory management for STL containers and attribute setters.
4648

FindRuby.cmake

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ supported.
1717
1818
Components
1919
^^^^^^^^^^
20-
.. versionadded:: 4.2.2
20+
21+
.. versionadded:: 4.3
2122
2223
This module supports the following components:
2324
@@ -32,7 +33,8 @@ are searched for.
3233
3334
Imported Targets
3435
^^^^^^^^^^^^^^^^
35-
.. versionadded:: 4.2.2
36+
37+
.. versionadded:: 4.3
3638
3739
This module defines the following :prop_tgt:`IMPORTED` targets:
3840
@@ -146,7 +148,7 @@ Finding Ruby and specifying the minimum required version:
146148
find_package(Ruby 3.2)
147149
#]=======================================================================]
148150

149-
#cmake_policy(GET CMP0185 _Ruby_CMP0185)
151+
cmake_policy(GET CMP0185 _Ruby_CMP0185)
150152

151153
if(NOT _Ruby_CMP0185 STREQUAL "NEW")
152154
# Backwards compatibility
@@ -175,14 +177,14 @@ set(_Ruby_POSSIBLE_EXECUTABLE_NAMES ruby)
175177
# If the user has not specified a Ruby version, create a list of Ruby versions
176178
# to search (newest to oldest). Based on https://www.ruby-lang.org/en/downloads/releases/
177179
if (NOT Ruby_FIND_VERSION_EXACT)
178-
set(_Ruby_SUPPORTED_VERSIONS 40 35 34 33 32)
180+
set(_Ruby_SUPPORTED_VERSIONS 40 34 33 32)
179181
set(_Ruby_UNSUPPORTED_VERSIONS 31 30 27 26 25 24 23 22 21 20)
180182
foreach (_ruby_version IN LISTS _Ruby_SUPPORTED_VERSIONS _Ruby_UNSUPPORTED_VERSIONS)
181183
string(SUBSTRING "${_ruby_version}" 0 1 _ruby_major_version)
182184
string(SUBSTRING "${_ruby_version}" 1 1 _ruby_minor_version)
183185
# Append both rubyX.Y and rubyXY (eg: ruby3.4 ruby34)
184-
list(APPEND _Ruby_POSSIBLE_EXECUTABLE_NAMES
185-
ruby${_ruby_major_version}.${_ruby_minor_version}
186+
list(APPEND _Ruby_POSSIBLE_EXECUTABLE_NAMES
187+
ruby${_ruby_major_version}.${_ruby_minor_version}
186188
ruby${_ruby_major_version}${_ruby_minor_version})
187189
endforeach ()
188190
endif ()

docs/architecture/incomplete_types.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,92 @@ Since Rice only stores pointers and never copies incomplete types by value, it d
182182
- Cannot access members of incomplete types directly
183183
- The incomplete type must be registered before any function using it is called
184184

185+
## Smart Pointers to Incomplete Types
186+
187+
While Rice supports raw pointers and references to incomplete types, **smart pointers** (`std::shared_ptr`, `std::unique_ptr`, etc.) require special handling.
188+
189+
### The Problem
190+
191+
Smart pointers need to instantiate their deleter at compile time. When you create a `std::shared_ptr<T>` from a raw `T*`, the template must generate code to `delete` the pointer - which requires `T` to be a complete type:
192+
193+
```cpp
194+
class Impl; // Forward declaration - incomplete
195+
196+
// This will cause a compiler warning/error:
197+
// "deletion of pointer to incomplete type; no destructor called"
198+
std::shared_ptr<Impl> ptr(new Impl); // Can't instantiate deleter!
199+
```
200+
201+
### Rice's Solution
202+
203+
Rice's `define_shared_ptr<T>()` function uses `is_complete_v<T>` to detect incomplete types and skip registering constructors that would require the complete type:
204+
205+
```cpp
206+
// From rice/stl/shared_ptr.ipp
207+
if constexpr (detail::is_complete_v<T> && !std::is_void_v<T>)
208+
{
209+
result.define_constructor(Constructor<SharedPtr_T, T*>(),
210+
Arg("value").takeOwnership());
211+
}
212+
```
213+
214+
This means:
215+
- **Complete types**: Full smart pointer support including construction from raw pointers
216+
- **Incomplete types**: Smart pointer type is registered, but constructors taking `T*` are skipped
217+
218+
### Passing Existing Smart Pointers
219+
220+
Even without the `T*` constructor, you can still pass around existing smart pointers that were created on the C++ side (where the complete type is available):
221+
222+
```cpp
223+
class Widget {
224+
public:
225+
struct Impl;
226+
std::shared_ptr<Impl> getImpl(); // Returns existing shared_ptr - OK!
227+
void setImpl(std::shared_ptr<Impl> impl); // Accepts existing shared_ptr - OK!
228+
private:
229+
std::shared_ptr<Impl> pImpl_;
230+
};
231+
```
232+
233+
### Custom Smart Pointer Types
234+
235+
If you're wrapping a library with its own smart pointer type (like OpenCV's `cv::Ptr`), apply the same pattern:
236+
237+
```cpp
238+
template<typename T>
239+
Data_Type<CustomPtr<T>> define_custom_ptr()
240+
{
241+
// ... setup ...
242+
243+
// Only define T* constructor for complete types
244+
if constexpr (detail::is_complete_v<T> && !std::is_void_v<T>)
245+
{
246+
result.define_constructor(Constructor<CustomPtr<T>, T*>(),
247+
Arg("ptr").takeOwnership());
248+
}
249+
250+
return result;
251+
}
252+
```
253+
254+
### Using is_complete_v
255+
256+
Rice provides the `detail::is_complete_v<T>` trait for detecting incomplete types:
257+
258+
```cpp
259+
#include <rice/rice.hpp>
260+
261+
class Complete { int x; };
262+
class Incomplete;
263+
264+
static_assert(Rice::detail::is_complete_v<Complete> == true);
265+
static_assert(Rice::detail::is_complete_v<Incomplete> == false);
266+
```
267+
185268
## See Also
186269
270+
- [Smart Pointers](smart_pointers.md) - Implementing support for custom smart pointer types
187271
- [Pointers](../bindings/pointers.md) - General pointer handling in Rice
188272
- [References](../bindings/references.md) - Reference handling in Rice
189273
- [Memory Management](../bindings/memory_management.md) - Object lifetime management

docs/architecture/smart_pointers.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,7 @@ namespace Rice
412412
}
413413
}
414414
```
415+
416+
## See Also
417+
418+
- [Incomplete Types](incomplete_types.md) - Handling forward-declared types, including smart pointers to incomplete types

docs/bindings/class_templates.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,47 +46,46 @@ typedef Mat_<Vec4i> Mat4i;
4646

4747
A naive approach to wrapping these classes is to define each one separately. Don't do that!
4848

49-
Instead, write a function to create wrappers. A simplified version looks like this:
49+
Instead, write a function template that creates and returns the wrapper:
5050

5151
```cpp
52-
template<typename Data_Type_T, typename _Tp>
53-
inline void Mat__builder(Data_Type_T& klass)
52+
template<typename _Tp>
53+
inline Data_Type<cv::Mat_<_Tp>> Mat__instantiate(VALUE module, const char* name)
5454
{
55-
klass.define_constructor(Constructor<cv::Mat_::Mat_<_Tp>>()).
56-
define_constructor(Constructor<cv::Mat_::Mat_<_Tp>, int, int>(), Arg("_rows"), Arg("_cols")).
55+
return define_class_under<cv::Mat_<_Tp>, cv::Mat>(module, name)
56+
.define_constructor(Constructor<cv::Mat_::Mat_<_Tp>>())
57+
.define_constructor(Constructor<cv::Mat_::Mat_<_Tp>, int, int>(), Arg("_rows"), Arg("_cols"))
5758

58-
template define_iterator<cv::Mat_<_Tp>::iterator(cv::Mat_<_Tp>::*)()>(&cv::Mat_<_Tp>::begin, &cv::Mat_<_Tp>::end, "each").
59-
template define_method<_Tp&(cv::Mat_<_Tp>::*)(int, int)>("[]", &cv::Mat_<_Tp>::operator(), Arg("row"), Arg("col")).
59+
.template define_iterator<typename cv::Mat_<_Tp>::iterator(cv::Mat_<_Tp>::*)()>(&cv::Mat_<_Tp>::begin, &cv::Mat_<_Tp>::end, "each")
60+
.template define_method<_Tp&(cv::Mat_<_Tp>::*)(int, int)>("[]", &cv::Mat_<_Tp>::operator(), Arg("row"), Arg("col"))
6061

61-
define_method("[]=", [](cv::Mat_<_Tp>& self, int row, int column, _Tp& value)
62+
.define_method("[]=", [](cv::Mat_<_Tp>& self, int row, int column, _Tp& value)
6263
{
6364
self(row, column) = value;
6465
});
65-
};
66+
}
6667
```
6768
68-
Then call this function using the `define` method Rice provides:
69+
Then call this function to instantiate each concrete class:
6970
7071
```cpp
71-
VALUE rb_cMat1b = define_class_under<cv::Mat_<unsigned char>, cv::Mat>(rb_mCv, "Mat1b").
72-
define(&Mat__builder<Data_Type<cv::Mat_<unsigned char>>, unsigned char>);
72+
VALUE rb_cMat1b = Mat__instantiate<unsigned char>(rb_mCv, "Mat1b");
7373
74-
VALUE rb_cMat2b = define_class_under<cv::Mat_<cv::Vec<unsigned char, 2>>, cv::Mat>(rb_mCv, "Mat2b").
75-
define(&Mat__builder<Data_Type<cv::Mat_<cv::Vec<unsigned char, 2>>>, cv::Vec<unsigned char, 2>>);
74+
VALUE rb_cMat2b = Mat__instantiate<cv::Vec<unsigned char, 2>>(rb_mCv, "Mat2b");
7675
7776
...
7877
```
7978

8079
There are few things to notice about the above code.
8180

82-
First, by convention, the method is named `"#{template_name}_builder"`. So in this case `Mat__builder`. You may of course name the method anything you want.
81+
First, by convention, the function is named `"#{template_name}_instantiate"`. So in this case `Mat__instantiate`. You may of course name the function anything you want.
8382

8483
Second, the `template` keyword needs to be used in front of methods:
8584

8685
```cpp
87-
template define_iterator<cv::Mat_<_Tp>::iterator(cv::Mat_<_Tp>::*)()>(&cv::Mat_<_Tp>::begin, &cv::Mat_<_Tp>::end, "each").
86+
.template define_iterator<typename cv::Mat_<_Tp>::iterator(cv::Mat_<_Tp>::*)()>(&cv::Mat_<_Tp>::begin, &cv::Mat_<_Tp>::end, "each")
8887

89-
template define_method<_Tp&(cv::Mat_<_Tp>::*)(int, int)>("[]", &cv::Mat_<_Tp>::operator(), Arg("row"), Arg("col")).
88+
.template define_method<_Tp&(cv::Mat_<_Tp>::*)(int, int)>("[]", &cv::Mat_<_Tp>::operator(), Arg("row"), Arg("col"))
9089
```
9190

9291
Third, the array constructor cannot be wrapped because it uses a template parameter that is not defined:
@@ -99,11 +98,12 @@ explicit Mat_(const std::array<_Tp, _Nm>& arr, bool copyData=false);
9998
Fourth, the `operator()` is mapped to two Ruby methods, `[]` and `[]=`.
10099
101100
```cpp
102-
template define_method<_Tp&(cv::Mat_<_Tp>::*)(int, int)>("[]", &cv::Mat_<_Tp>::operator(), Arg("row"), Arg("col")).
103-
define_method("[]=", [](cv::Mat_<_Tp>& self, int row, int column, _Tp& value)
104-
{
105-
self(row, column) = value;
106-
});
101+
.template define_method<_Tp&(cv::Mat_<_Tp>::*)(int, int)>("[]", &cv::Mat_<_Tp>::operator(), Arg("row"), Arg("col"))
102+
103+
.define_method("[]=", [](cv::Mat_<_Tp>& self, int row, int column, _Tp& value)
104+
{
105+
self(row, column) = value;
106+
});
107107
```
108108

109-
Once you have created a class builder function it is easy to create new C++ classes from class templates and wrap them in Ruby.
109+
Once you have created an instantiation function it is easy to create new C++ classes from class templates and wrap them in Ruby.

docs/bindings/operators.md

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,40 @@ C++ and Ruby support overriding the same arithmetic operators.
2424
| / | / |
2525
| % | % |
2626

27+
## Unary Operators
28+
29+
C++ supports unary versions of `+`, `-`, `~`, and `!`. Ruby uses special method names for unary `+` and `-` to distinguish them from their binary counterparts.
30+
31+
| C++ | Ruby | Notes |
32+
|:---:|:----:|:------|
33+
| +a | +@ | Unary plus |
34+
| -a | -@ | Unary minus (negation) |
35+
| ~a | ~ | Bitwise NOT |
36+
| !a | ! | Logical NOT |
37+
38+
Example:
39+
40+
```cpp
41+
class Vector
42+
{
43+
public:
44+
Vector operator-() const; // Unary minus
45+
Vector operator+() const; // Unary plus
46+
};
47+
```
48+
49+
```cpp
50+
define_method("-@", &Vector::operator-);
51+
define_method("+@", &Vector::operator+);
52+
```
53+
54+
In Ruby:
55+
56+
```ruby
57+
v = Vector.new(1, 2, 3)
58+
negated = -v # Calls -@
59+
```
60+
2761
## Assignment Operators
2862

2963
C++ supports overriding assignment operators while Ruby does not. Thus these operators must be mapped to Ruby methods.
@@ -35,7 +69,7 @@ C++ supports overriding assignment operators while Ruby does not. Thus these ope
3569
| -= | Not overridable | assign_minus |
3670
| *= | Not overridable | assign_multiply |
3771
| /= | Not overridable | assign_divide |
38-
| %= | Not overridable | assign_plus |
72+
| %= | Not overridable | assign_modulus |
3973

4074
## Bitwise Operators
4175

@@ -57,8 +91,8 @@ C++ and Ruby support overriding the same comparison operators.
5791
|:---:|:----:|
5892
| == | == |
5993
| != | != |
60-
| > | < |
61-
| < | > |
94+
| > | > |
95+
| < | < |
6296
| >= | >= |
6397
| <= | <= |
6498

@@ -70,18 +104,18 @@ Ruby allows the `!` operator to be overridden but not `&&` or `||`.
70104
|:------:|:----------------:|:------------|
71105
| && | Not overridable | logical_and |
72106
| \|\| | Not overridable | logical_or |
73-
| ! | ! | decrement_pre |
107+
| ! | ! | |
74108

75109
## Increment / Decrement Operators
76110

77111
C++ supports increment and decrement operators while Ruby does not. Thus these operators must be mapped to Ruby methods.
78112

79113
| C++ | Ruby | Ruby Method |
80114
|:----:|:----------------:|:---------------|
81-
| ++a | Not overridable | increment_pre |
82-
| a++ | Not overridable | increment |
83-
| --a | Not overridable | decrement_pre |
84-
| a-- | Not overridable | decrement |
115+
| ++a | Not overridable | increment |
116+
| a++ | Not overridable | increment_post |
117+
| --a | Not overridable | decrement |
118+
| a-- | Not overridable | decrement_post |
85119

86120
## Other Operators
87121

rice/Data_Type.hpp

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,27 +62,6 @@ namespace Rice
6262
template<typename Constructor_T, typename...Rice_Arg_Ts>
6363
Data_Type<T>& define_constructor(Constructor_T constructor, Rice_Arg_Ts const& ...args);
6464

65-
/*! Runs a function that should define this Data_Types methods and attributes.
66-
* This is useful when creating classes from a C++ class template.
67-
*
68-
* \param builder A function that addes methods/attributes to this class
69-
*
70-
* For example:
71-
* \code
72-
* void builder(Data_Type<Matrix<T, R, C>>& klass)
73-
* {
74-
* klass.define_method...
75-
* return klass;
76-
* }
77-
*
78-
* define_class<<Matrix<T, R, C>>>("Matrix")
79-
* .build(&builder);
80-
*
81-
* \endcode
82-
*/
83-
template<typename Func_T>
84-
Data_Type<T>& define(Func_T func);
85-
8665
//! Register a Director class for this class.
8766
/*! For any class that uses Rice::Director to enable polymorphism
8867
* across the languages, you need to register that director proxy
@@ -133,11 +112,11 @@ namespace Rice
133112
template<typename Iterator_Func_T>
134113
Data_Type<T>& define_iterator(Iterator_Func_T begin, Iterator_Func_T end, std::string name = "each");
135114

136-
template <typename Attribute_T, typename...Arg_Ts>
137-
Data_Type<T>& define_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite, const Arg_Ts&...args);
138-
139-
template <typename Attribute_T, typename...Arg_Ts>
140-
Data_Type<T>& define_singleton_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite, const Arg_Ts&...args);
115+
template <typename Attribute_T, typename Access_T = AttrAccess::ReadWriteType, typename...Arg_Ts>
116+
Data_Type<T>& define_attr(std::string name, Attribute_T attribute, Access_T access = {}, const Arg_Ts&...args);
117+
118+
template <typename Attribute_T, typename Access_T = AttrAccess::ReadWriteType, typename...Arg_Ts>
119+
Data_Type<T>& define_singleton_attr(std::string name, Attribute_T attribute, Access_T access = {}, const Arg_Ts&...args);
141120

142121
#include "cpp_api/shared_methods.hpp"
143122
protected:
@@ -163,8 +142,8 @@ namespace Rice
163142
template<typename Method_T, typename...Arg_Ts>
164143
void wrap_native_method(VALUE klass, std::string name, Method_T&& function, const Arg_Ts&...args);
165144

166-
template <typename Attribute_T, typename...Arg_Ts>
167-
Data_Type<T>& define_attr_internal(VALUE klass, std::string name, Attribute_T attribute, AttrAccess access, const Arg_Ts&...args);
145+
template <typename Attribute_T, typename Access_T, typename...Arg_Ts>
146+
Data_Type<T>& define_attr_internal(VALUE klass, std::string name, Attribute_T attribute, Access_T access, const Arg_Ts&...args);
168147

169148
private:
170149
template<typename T_>

0 commit comments

Comments
 (0)