Overloaded Methods

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

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(Rect)
  {
    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);

Instead, you need to tell the C++ compiler which overloaded method to use. There are several ways you can 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);

size_t(Container::*)() and void(Container::*)(size_t) are C++ pointers to member functions.

Using

Another solution is via C++ using functionality, 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 are old school, and like obtuse syntax, you can also use a 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);
}

Multiple Methods

Many C++ classes have multiple methods with the same name. For example, consider this C++ class which defines three intersection methods:

class Shape
{
public:
  bool intersects(const Rectangle& bounds);
  bool intersects(const Polygon& shape);
  bool intersects(int xMin, int yMin, int xMax, int yMax);
};

As explained above, the easiest way to wrap this class is specify the correct template parameters to the define_method call:

Class c = define_class<Shape>("Shape")
 .define_constructor(Constructor<Shape>())
 .define_method<bool(Shape::*)(const Rectangle&)>("intersects", &Shape::intersects)
 .define_method<bool(Shape::*)(const Polygon&)>("intersects", &Shape::intersects)
 .define_method<bool(Shape::*)(int, int, int, int)>("intersects", &Shape::intersects);

Method Resolution

Ruby does not natively support method overloading. Thus Rice must implement overloading support itself. It does this by maintaining a global registry (see NativeRegistry) of methods keyed on class and method name. Thus for the example above, the key is Shape::intersects and the value is an array of three NativeFunction instances, where each NativeFunction instance maps to one C++ member function.

At runtime, Rice evaluates the method parameters sent to the intersects method and determines the best match. It does this by looping over the three NativeFunction instances and calls their matches method. The matches method, in turn, loops over the passed-in parameters and sends them to its array of From_Ruby instances (for more information see the type conversion section).

Each From_Ruby instance defines a convertible method that returns one of three results:

  • Exact - The types match exactly

  • Typecast - The types match but require conversion (for example an integer to a double)

  • None - The type cannot be converted (for example a string to an integer)

Based on the results for each parameter, each overloaded C++ method is sorted from best match to worst match. The best matching function is then called.