Skip to content

Commit 3d509f8

Browse files
authored
Merge pull request #364 from ruby-rice/dev
Documentation updates.
2 parents 8bf7448 + b2ddb69 commit 3d509f8

File tree

13 files changed

+494
-55
lines changed

13 files changed

+494
-55
lines changed

.github/workflows/docs.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Deploy Documentation
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
workflow_dispatch: # Allow manual triggering
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Set up Python
15+
uses: actions/setup-python@v5
16+
with:
17+
python-version: '3.x'
18+
19+
- name: Install mkdocs and dependencies
20+
run: |
21+
pip install mkdocs-material
22+
23+
- name: Build documentation
24+
run: mkdocs build --strict
25+
26+
- name: Deploy to GitHub Pages
27+
uses: peaceiris/actions-gh-pages@v4
28+
with:
29+
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
30+
external_repository: ruby-rice/ruby-rice.github.io
31+
publish_branch: main
32+
publish_dir: ./site
33+
destination_dir: 4.x
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
# Overload Resolution
2+
3+
This document explains how Rice resolves C++ method overloads when called from Ruby.
4+
5+
## Overview
6+
7+
When a C++ class has multiple methods with the same name but different parameter types (overloads), Rice must determine which one to call based on the Ruby arguments provided. Rice uses a numeric scoring system where each overload receives a score from 0.0 to 1.0, and the highest-scoring overload is selected.
8+
9+
## Scoring Constants
10+
11+
The scoring system is defined in `rice/detail/from_ruby.hpp`:
12+
13+
```cpp
14+
struct Convertible
15+
{
16+
static constexpr double Exact = 1.0; // Perfect type match
17+
static constexpr double None = 0.0; // Cannot convert
18+
static constexpr double IntToFloat = 0.9; // Penalty for int to float conversion
19+
static constexpr double SignedToUnsigned = 0.5;// Penalty for signed to unsigned (can't represent negatives)
20+
static constexpr double FloatToInt = 0.5; // Penalty for float to int conversion
21+
static constexpr double ConstMismatch = 0.99; // Penalty for const mismatch
22+
};
23+
```
24+
25+
## Precision Bits
26+
27+
Ruby numeric types have precision defined in terms of bits:
28+
29+
| Ruby Type | Precision Bits | Notes |
30+
|-----------|----------------|-------|
31+
| Integer (Fixnum/Bignum) | 63 | Same as C++ long long |
32+
| Float | 53 | Same as C++ double mantissa |
33+
34+
C++ types use `std::numeric_limits<T>::digits`:
35+
36+
| C++ Type | Precision Bits |
37+
|----------|----------------|
38+
| char | 7 |
39+
| signed char | 7 |
40+
| unsigned char | 8 |
41+
| short | 15 |
42+
| unsigned short | 16 |
43+
| int | 31 |
44+
| unsigned int | 32 |
45+
| long | 31 or 63* |
46+
| unsigned long | 32 or 64* |
47+
| long long | 63 |
48+
| unsigned long long | 64 |
49+
| float | 24 (mantissa) |
50+
| double | 53 (mantissa) |
51+
52+
\* Platform dependent
53+
54+
## Same-Domain Conversions
55+
56+
When converting within the same numeric domain (integer-to-integer or float-to-float):
57+
58+
**Widening conversion (target >= source bits):** Score = 1.0
59+
60+
**Narrowing conversion (target < source bits):** Score = targetBits / sourceBits
61+
62+
Example: Ruby Integer (63 bits) to C++ short (15 bits)
63+
```
64+
Score = 15 / 63 = 0.238
65+
```
66+
67+
### Signed to Unsigned
68+
69+
When converting a Ruby Integer (which is signed) to an unsigned C++ type, a penalty is applied because unsigned types cannot represent negative values:
70+
71+
```
72+
Score = precisionScore * SignedToUnsigned
73+
= precisionScore * 0.5
74+
```
75+
76+
Example: Ruby Integer (63 bits) to C++ unsigned int (32 bits)
77+
```
78+
precisionScore = 32 / 63 = 0.508
79+
Score = 0.508 * 0.5 = 0.254
80+
```
81+
82+
This ensures signed types are preferred over unsigned types. For example, given overloads `foo(int)` and `foo(unsigned int)`:
83+
84+
| Overload | Score | Calculation |
85+
|----------|-------|-------------|
86+
| foo(int) | 0.49 | 31/63 |
87+
| foo(unsigned int) | 0.25 | 32/63 × 0.5 |
88+
89+
Result: `foo(int)` is selected.
90+
91+
## Cross-Domain Conversions
92+
93+
### Integer to Float
94+
95+
When converting a Ruby Integer to a C++ float type, the score combines precision loss with a domain-change penalty:
96+
97+
```
98+
Score = precisionScore * IntToFloat
99+
= precisionScore * 0.9
100+
```
101+
102+
Example: Ruby Integer (63 bits) to C++ double (53 bits)
103+
```
104+
precisionScore = min(63, 53) / 63 = 53/63 = 0.841
105+
Score = 0.841 * 0.9 = 0.757
106+
```
107+
108+
Example: Ruby Integer (63 bits) to C++ float (24 bits)
109+
```
110+
precisionScore = min(63, 24) / 63 = 24/63 = 0.381
111+
Score = 0.381 * 0.9 = 0.343
112+
```
113+
114+
### Float to Integer
115+
116+
When converting a Ruby Float to a C++ integer type, a larger penalty is applied because the fractional part is lost:
117+
118+
```
119+
Score = precisionScore * FloatToInt
120+
= precisionScore * 0.5
121+
```
122+
123+
Example: Ruby Float (53 bits) to C++ int (31 bits)
124+
```
125+
precisionScore = 31 / 53 = 0.585
126+
Score = 0.585 * 0.5 = 0.292
127+
```
128+
129+
Example: Ruby Float (53 bits) to C++ long long (63 bits)
130+
```
131+
precisionScore = 53 / 53 = 1.0 (capped, since 63 >= 53)
132+
Score = 1.0 * 0.5 = 0.5
133+
```
134+
135+
## Type Mapping Reference
136+
137+
The following table shows conversion scores for all Ruby-to-C++ type combinations, with the underlying calculations.
138+
139+
| C++ Type | Bits | True | False | Nil | String | Integer | Float |
140+
|--------------------|:----:|:----:|:-----:|:---:|:------:|:-------:|:-----:|
141+
| bool | - | 1.0 | 1.0 | 1.0 | | | |
142+
| char | 7 | | | | 1.0 | 0.11 = 7/63 | 0.07 = 7/53×0.5 |
143+
| signed char | 7 | | | | 1.0 | 0.11 = 7/63 | 0.07 = 7/53×0.5 |
144+
| unsigned char | 8 | | | | 1.0 | 0.06 = 8/63×0.5 | 0.08 = 8/53×0.5 |
145+
| short | 15 | | | | | 0.24 = 15/63 | 0.14 = 15/53×0.5 |
146+
| unsigned short | 16 | | | | | 0.13 = 16/63×0.5 | 0.15 = 16/53×0.5 |
147+
| int | 31 | | | | | 0.49 = 31/63 | 0.29 = 31/53×0.5 |
148+
| unsigned int | 32 | | | | | 0.25 = 32/63×0.5 | 0.30 = 32/53×0.5 |
149+
| long* | 31 | | | | | 0.49 = 31/63 | 0.29 = 31/53×0.5 |
150+
| unsigned long* | 32 | | | | | 0.25 = 32/63×0.5 | 0.30 = 32/53×0.5 |
151+
| long long | 63 | | | | | 1.0 = 63/63 | 0.50 = 1.0×0.5 |
152+
| unsigned long long | 64 | | | | | 0.50 = 1.0×0.5 | 0.50 = 1.0×0.5 |
153+
| float | 24 | | | | | 0.34 = 24/63×0.9 | 0.45 = 24/53 |
154+
| double | 53 | | | | | 0.76 = 53/63×0.9 | 1.0 = 53/53 |
155+
156+
\* `long` is platform-dependent. On 64-bit systems: `long` = 63 bits, `unsigned long` = 64 bits.
157+
158+
**Score formulas:**
159+
- **Integer → signed integer**: `targetBits / 63` (narrowing) or `1.0` (widening)
160+
- **Integer → unsigned integer**: `(targetBits / 63) × 0.5` (SignedToUnsigned penalty)
161+
- **Integer → float**: `min(targetBits, 63) / 63 × 0.9` (IntToFloat penalty)
162+
- **Float → integer**: `min(targetBits, 53) / 53 × 0.5` (FloatToInt penalty)
163+
- **Float → float**: `targetBits / 53` (narrowing) or `1.0` (widening)
164+
165+
## Default Parameters
166+
167+
When an overload has default parameters and the caller does not provide all arguments, a small penalty is applied for each default used:
168+
169+
```
170+
parameterMatch = 0.99 ^ defaultCount
171+
```
172+
173+
This ensures that overloads that exactly match the argument count are preferred over those that rely on defaults.
174+
175+
## Final Score Calculation
176+
177+
The final score for an overload is:
178+
179+
```
180+
finalScore = minParameterScore * parameterMatch
181+
```
182+
183+
Where:
184+
- `minParameterScore` is the minimum score across all passed arguments
185+
- `parameterMatch` is the penalty for default parameters (0.99 per default)
186+
187+
Using the minimum ensures that one bad match cannot be hidden by good matches elsewhere.
188+
189+
## Const Correctness
190+
191+
When a Ruby-wrapped C++ object is passed as an argument:
192+
193+
- Passing a const object to a non-const parameter: Score = 0.0 (not allowed)
194+
- Passing a non-const object to a const parameter: Score *= 0.99 (small penalty)
195+
- Matching constness: No penalty
196+
197+
## Resolution Process
198+
199+
The resolution happens in `rice/detail/Native.ipp`:
200+
201+
1. `Native::resolve()` is called when Ruby invokes a method
202+
2. For each registered overload, `Native::matches()` computes a score
203+
3. Overloads are sorted by score (highest first)
204+
4. The highest-scoring overload is selected
205+
5. If no overload scores above 0.0, an error is raised
206+
207+
## Examples
208+
209+
### Example 1: Exact Match vs Type Conversion
210+
211+
Given these C++ overloads:
212+
```cpp
213+
void foo(int x);
214+
void foo(double x);
215+
```
216+
217+
Called with Ruby Integer `foo(42)`:
218+
219+
| Overload | Parameter Score | Final Score |
220+
|----------|-----------------|-------------|
221+
| foo(int) | 1.0 (exact) | 1.0 |
222+
| foo(double) | 0.9 (int to float) | 0.9 |
223+
224+
Result: `foo(int)` is selected.
225+
226+
Called with Ruby Float `foo(3.14)`:
227+
228+
| Overload | Parameter Score | Final Score |
229+
|----------|-----------------|-------------|
230+
| foo(int) | 0.5 (float to int) | 0.5 |
231+
| foo(double) | 1.0 (exact) | 1.0 |
232+
233+
Result: `foo(double)` is selected.
234+
235+
### Example 2: Default Parameters
236+
237+
Given these C++ overloads:
238+
```cpp
239+
void bar(int x);
240+
void bar(int x, int y = 0);
241+
```
242+
243+
Called with `bar(1)`:
244+
245+
| Overload | Min Score | Param Match | Final Score |
246+
|----------|-----------|-------------|-------------|
247+
| bar(int) | 1.0 | 1.0 | 1.0 |
248+
| bar(int, int=0) | 1.0 | 0.99 | 0.99 |
249+
250+
Result: `bar(int)` is selected because it has no defaults.
251+
252+
Called with `bar(1, 2)`:
253+
254+
| Overload | Min Score | Param Match | Final Score |
255+
|----------|-----------|-------------|-------------|
256+
| bar(int) | N/A | N/A | 0.0 (too many args) |
257+
| bar(int, int=0) | 1.0 | 1.0 | 1.0 |
258+
259+
Result: `bar(int, int=0)` is selected.
260+
261+
### Example 3: Precision-Based Selection
262+
263+
Given these C++ overloads:
264+
```cpp
265+
void baz(short x);
266+
void baz(long long x);
267+
```
268+
269+
Called with Ruby Integer `baz(1)`:
270+
271+
| Overload | Parameter Score | Final Score |
272+
|----------|-----------------|-------------|
273+
| baz(short) | 15/63 = 0.238 | 0.238 |
274+
| baz(long long) | 63/63 = 1.0 | 1.0 |
275+
276+
Result: `baz(long long)` is selected because it can hold the full precision.
277+
278+
### Example 4: Mixed Arguments
279+
280+
Given this C++ overload:
281+
```cpp
282+
void qux(int a, double b);
283+
void qux(double a, int b);
284+
```
285+
286+
Called with `qux(1, 2.0)` (Integer, Float):
287+
288+
| Overload | Scores | Min Score | Final |
289+
|----------|--------|-----------|-------|
290+
| qux(int, double) | 1.0, 1.0 | 1.0 | 1.0 |
291+
| qux(double, int) | 0.9, 0.5 | 0.5 | 0.5 |
292+
293+
Result: `qux(int, double)` is selected.
294+
295+
Called with `qux(1.0, 2)` (Float, Integer):
296+
297+
| Overload | Scores | Min Score | Final |
298+
|----------|--------|-----------|-------|
299+
| qux(int, double) | 0.5, 0.9 | 0.5 | 0.5 |
300+
| qux(double, int) | 1.0, 1.0 | 1.0 | 1.0 |
301+
302+
Result: `qux(double, int)` is selected.
303+
304+
### Example 5: Const Correctness
305+
306+
Given these C++ overloads:
307+
```cpp
308+
void process(MyClass& obj);
309+
void process(const MyClass& obj);
310+
```
311+
312+
Called with a non-const Ruby-wrapped MyClass:
313+
314+
| Overload | Score |
315+
|----------|-------|
316+
| process(MyClass&) | 1.0 |
317+
| process(const MyClass&) | 0.99 |
318+
319+
Result: `process(MyClass&)` is selected.
320+
321+
Called with a const Ruby-wrapped MyClass:
322+
323+
| Overload | Score |
324+
|----------|-------|
325+
| process(MyClass&) | 0.0 (not allowed) |
326+
| process(const MyClass&) | 1.0 |
327+
328+
Result: `process(const MyClass&)` is selected.
329+
330+
## Key Files
331+
332+
- `rice/detail/from_ruby.hpp` - Defines Convertible constants
333+
- `rice/detail/from_ruby.ipp` - Implements rubyPrecisionBits(), precisionScore(), and FromRubyFundamental
334+
- `rice/detail/Native.hpp` - Defines Resolved struct and Native class
335+
- `rice/detail/Native.ipp` - Implements resolve(), matches(), and matchParameters()
336+
- `rice/detail/Parameter.hpp` - Defines Parameter class
337+
- `rice/detail/Parameter.ipp` - Implements Parameter::matches()
338+
339+
## See Also
340+
341+
- `test/test_Overloads.cpp` - Test cases for overload resolution

docs/architecture/overview.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ Rice is organized into several subsystems:
6262
[Method Binding](method_binding.md)
6363
`Native` hierarchy - Binding C++ functions and methods to Ruby.
6464

65+
[Overload Resolution](overload_resolution.md)
66+
How Rice selects the correct C++ overload based on Ruby arguments using precision-based scoring.
67+
6568
[Types Overview](../types/overview.md)
6669
`From_Ruby<T>` and `To_Ruby<T>` - Converting values between languages.
6770

docs/bindings/buffers.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ Buffer's have the following Ruby API:
104104
* bytes(count) - A Ruby string with a binary encoding of the specified length
105105
* to_ary - A Ruby array. Buffer#size must be set
106106
* to_ary(count) - A Ruby array of the specified length
107-
* [](index) - Get the item at the specified index
108-
* []=(index) - Update the item at the specified index
107+
* `[](index)` - Get the item at the specified index
108+
* `[]=(index)` - Update the item at the specified index
109109
* data - Get a `Pointer<T>` object to the Buffer's managed memory that can be passed to C++ APIs
110110
* release - Same as #data but tells the buffer to release ownership of its memory

0 commit comments

Comments
 (0)