|
| 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 |
0 commit comments