Skip to content

Overloaded Methods

C++ supports overloading constructors, methods and functions. Starting with version 4.5, Rice supports C++ method overloading.

When you try to wrap an overloaded function the C++ compiler will throw an error message that says something like "no matching overloaded function found."

Getters and Setters

For example, consider this C++ class with a getter and setter that have the same name:

class Container
{
public:
  size_t capacity()
  {
    return this->capacity_;
  }

  void capacity(size_t value)
  {
    this->capacity_ = value;
  }

private:
  size_t capacity_;
};

If you try and wrap the class like this you will get a compiler error:

 Class c = define_class<Container>("Container")
  .define_constructor(Constructor<Container>())
  .define_method("capacity", &Container::capacity)
  .define_method("capacity=", &Container::capacity);

You need to tell the C++ compiler which overloaded method to use. There are several ways to do this as explained below.

Template Parameter

define_method is a template function, therefore one solution is to specify which method you are trying to call like this:

 Class c = define_class<Container>("Container")
  .define_constructor(Constructor<Container>())
  .define_method<size_t(Container::*)()>("capacity", &Container::capacity)
  .define_method<void(Container::*)(size_t)>("capacity=", &Container::capacity);

Notice the addition of the template specializations in side the < and '>' brackets. In this case, <size_t(Container::*)()> and <void(Container::*)(size_t)> are C++ pointers to member functions.

Using

Another solution is provided by the C++ keyword using, like this:

using Getter_T = size_t(Container::*)();
using Setter_T = void(Container::*)(size_t);

Class c = define_class<Container>("Container")
  .define_constructor(Constructor<Container>())
  .define_method<Getter_T>("capacity", &Container::capacity)
  .define_method<Setter_T>("capacity=", &Container::capacity);

Or even like this:

using Getter_T = size_t(Container::*)();
using Setter_T = void(Container::*)(size_t);

Class c = define_class<Container>("Container")
  .define_constructor(Constructor<Container>())
  .define_method("capacity", (Getter_T)&Container::capacity)
  .define_method("capacity=", (Setter_T)&Container::capacity);

Typedef

If you like old school, obtuse C syntax, then use typedef like this:

extern "C"
void Init_Container()
{
    typedef size_t(Container::* Getter_T)();
    typedef void (Container::* Setter_T)(size_t);

    Class c = define_class<Container>("Container")
      .define_constructor(Constructor<Container>())
      .define_method("capacity", (Getter_T)&Container::capacity)
      .define_method("capacity=", (Setter_T)&Container::capacity);
}

Method Resolution

Ruby does not natively support method overloading. Thus Rice implements overloading support itself. It does this by maintaining a global registry (see NativeRegistry) of methods keyed on class and method name. For the example above, the key would be Container::capacity and the value is an array of two NativeFunction instances, where each NativeFunction instance maps to one C++ member function.

At runtime, Rice evaluates the method parameters sent from Ruby and determines which overloaded C++ method is the best match. It does this by looping over the native NativeFunction instances and calls their matches method. The matches method, in turn, loops over the passed-in parameters and evaluates each one (for more information see the type conversion section).

Matches are scored on a scale of 0.0 to 1.0:

  • 1.0 (Exact) - The types match exactly
  • 0.99 (ConstMismatch) - Passing a non-const value to a const parameter
  • 0.9 (IntToFloat) - Domain change penalty when converting integer to float
  • 0.5 (SignedToUnsigned) - Penalty when converting signed Ruby Integer to unsigned C++ type
  • 0.5 (FloatToInt) - Domain change penalty when converting float to integer (lossy)
  • 0.0 (None) - The types do not match and cannot be converted

For numeric types, Rice uses precision-based scoring. The score is calculated as targetBits / sourceBits when narrowing (e.g., Ruby Integer with 63 bits to C++ int with 31 bits scores 31/63 ≈ 0.49). Cross-domain conversions (int↔float) multiply the precision score by the domain penalty.

The final score for each overload is: min(all parameter scores) × 0.99^(number of defaults used).

Based on these scores, each overloaded C++ method is sorted from best match to worst match. The best matching function is then called.

For more in-depth information about the resolution algorithm, see the Overload Resolution Architecture documentation.

Type Mapping

The following table shows how Ruby types are mapped to C++ types. Ruby Integer has 63 bits precision (like long long), and Ruby Float has 53 bits (like double mantissa).

C++ Type True False Nil String Integer Float
bool 1.0 1.0 1.0
char 1.0 0.11 0.07
signed char 1.0 0.11 0.07
unsigned char 1.0 0.06 0.08
short 0.24 0.14
unsigned short 0.13 0.15
int 0.49 0.29
unsigned int 0.25 0.30
long* 0.49 0.29
unsigned long* 0.25 0.30
long long 1.0 0.50
unsigned long long 0.50 0.50
float 0.34 0.45
double 0.76 1.0

* long is platform-dependent. On 64-bit systems: long = 63 bits (score 1.0), unsigned long = 64 bits (score 0.50).

See the Type Mapping Reference for the underlying calculations.

If multiple overloads have equal scores, the first one defined wins.