Skip to content

Commit

Permalink
Merge pull request #224 from ruby-rice/pointer_to_pointer
Browse files Browse the repository at this point in the history
Support pointers to pointers
  • Loading branch information
cfis authored Nov 21, 2024
2 parents 961e58e + e7479d3 commit f2b1772
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 13 deletions.
2 changes: 1 addition & 1 deletion rice/Data_Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ namespace Rice::detail
{
// Note that T could be a pointer or reference to a base class while data is in fact a
// child class. Lookup the correct type so we return an instance of the correct Ruby class
std::pair<VALUE, rb_data_type_t*> rubyTypeInfo = detail::Registries::instance.types.figureType(*data);
std::pair<VALUE, rb_data_type_t*> rubyTypeInfo = detail::Registries::instance.types.figureType(**data);
bool isOwner = this->returnInfo_ && this->returnInfo_->isOwner();
return detail::wrap(rubyTypeInfo.first, rubyTypeInfo.second, data, isOwner);
}
Expand Down
3 changes: 3 additions & 0 deletions rice/detail/NativeFunction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ namespace Rice::detail
// Convert Ruby argv pointer to Ruby values
std::vector<VALUE> getRubyValues(int argc, VALUE* argv);

template<typename Arg_T, int I>
Arg_T getNativeValue(std::vector<VALUE>& values);

// Convert Ruby values to C++ values
template<typename std::size_t...I>
Arg_Ts getNativeValues(std::vector<VALUE>& values, std::index_sequence<I...>& indices);
Expand Down
31 changes: 27 additions & 4 deletions rice/detail/NativeFunction.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,38 @@ namespace Rice::detail
return rbScanValues;
}

template<typename Class_T, typename Function_T, bool IsMethod>
template<typename Arg_T, int I>
Arg_T NativeFunction<Class_T, Function_T, IsMethod>::getNativeValue(std::vector<VALUE>& values)
{
/* In general the compiler will convert T to const T, but that does not work for converting
T** to const T** (see see https://isocpp.org/wiki/faq/const-correctness#constptrptr-conversion)
which comes up in the OpenCV bindings.
An alternative solution is updating From_Ruby#convert to become a templated function that specifies
the return type. That works but requires a lot more code changes for this one case and is not
backwards compatible. */
if constexpr (is_pointer_pointer_v<Arg_T> && !std::is_convertible_v<remove_cv_recursive_t<Arg_T>, Arg_T>)
{
return (Arg_T)std::get<I>(this->fromRubys_).convert(values[I]);
}
else
{
return std::get<I>(this->fromRubys_).convert(values[I]);
}
}

template<typename Class_T, typename Function_T, bool IsMethod>
template<std::size_t... I>
typename NativeFunction<Class_T, Function_T, IsMethod>::Arg_Ts NativeFunction<Class_T, Function_T, IsMethod>::getNativeValues(std::vector<VALUE>& values,
std::index_sequence<I...>& indices)
{
// Convert each Ruby value to its native value by calling the appropriate fromRuby instance.
// Note that for fundamental types From_Ruby<Arg_Ts> will keep a copy of the native value
// so it can be passed by reference or pointer to a native function.
return std::forward_as_tuple(std::get<I>(this->fromRubys_).convert(values[I])...);
/* Loop over each value returned from Ruby and convert it to the appropriate C++ type based
on the arguments (Arg_Ts) required by the C++ function. Arg_T may have const/volatile while
the associated From_Ruby<T> template parameter will not. Thus From_Ruby produces non-const values
which we let the compiler convert to const values as needed. This works except for
T** -> const T**, see comment in getNativeValue method. */
return std::forward_as_tuple(this->getNativeValue<std::tuple_element_t<I, Arg_Ts>, I>(values)...);
}

template<typename Class_T, typename Function_T, bool IsMethod>
Expand Down
19 changes: 19 additions & 0 deletions rice/traits/rice_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@ namespace Rice
{
using type = std::tuple<T<remove_cv_recursive_t<Arg_Ts>>...>;
};

template<class T>
struct is_pointer_pointer : std::false_type {};

template<class T>
struct is_pointer_pointer<T**> : std::true_type {};

template<class T>
struct is_pointer_pointer<T** const> : std::true_type {};

template<class T>
struct is_pointer_pointer<T* const * const> : std::true_type {};

template<class T>
struct is_pointer_pointer<const T* const* const> : std::true_type {};

template<class T>
constexpr bool is_pointer_pointer_v = is_pointer_pointer<T>::value;

} // detail
} // Rice

Expand Down
95 changes: 87 additions & 8 deletions test/test_Data_Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ SETUP(Data_Type)
embed_ruby();
}

/**
* The tests here are for the feature of taking an instance
* of a Ruby-subclass of a Rice wrapped class and passing
* that instance back into the Rice wrapper. While that
* might be confusing, the test code is pretty straight foward
* to see what we're talking about.
*/

namespace
{
class MyClass
Expand Down Expand Up @@ -477,6 +469,11 @@ namespace
return helper;
}

const Helper* passThroughConst(const Helper* helper)
{
return helper;
}

Helper* passThrough(void* helper)
{
return static_cast<Helper*>(helper);
Expand Down Expand Up @@ -511,6 +508,7 @@ TESTCASE(pointers)
Class myClass = define_class<MyClass2>("MyClass2")
.define_constructor(Constructor<MyClass2>())
.define_method<Helper*(MyClass2::*)(Helper*)>("pass_through", &MyClass2::passThrough)
.define_method<const Helper*(MyClass2::*)(const Helper*)>("pass_through_const", &MyClass2::passThroughConst)
.define_method<Helper*(MyClass2::*)(void*)>("pass_through_void", &MyClass2::passThrough)
.define_method<void*(MyClass2::*)()>("return_void_helper", &MyClass2::returnVoidHelper)
.define_method<bool(MyClass2::*)(void*)>("check_void_helper", &MyClass2::checkVoidHelper);
Expand All @@ -525,6 +523,10 @@ TESTCASE(pointers)
Object value = result.call("value");
ASSERT_EQUAL(5, detail::From_Ruby<int>().convert(value));

result = object.call("pass_through_const", helper);
value = result.call("value");
ASSERT_EQUAL(5, detail::From_Ruby<int>().convert(value));

result = object.call("pass_through_void", nullptr);
ASSERT_EQUAL(Qnil, result.value());

Expand All @@ -537,6 +539,83 @@ TESTCASE(pointers)
ASSERT_EQUAL(Qtrue, result.value());
}

namespace
{
class BigObject
{
public:
BigObject(int value): value(value)
{
}

public:
int value;
};

class Processor
{
public:
BigObject** createBigObjects(size_t size)
{
BigObject** result = new BigObject*[size];

for (int i = 0; i < size; ++i)
{
result[i] = new BigObject(i + 5);
}
return result;
}

int sumBigObjects(BigObject** bigObjects, size_t size)
{
int result = 0;

for (int i = 0; i < size; i++)
{
result += bigObjects[i]->value;
}
return result;
}

int sumBigObjectsConst(const BigObject** bigObjects, size_t size)
{
int result = 0;

for (int i = 0; i < size; i++)
{
result += bigObjects[i]->value;
}
return result;
}

private:
BigObject** bigObjects_ = nullptr;
};

}

TESTCASE(pointerToPointer)
{
Class BigObjectClass = define_class<BigObject>("BigObject")
.define_attr("value", &BigObject::value);

Class ProcessorClass = define_class<Processor>("ProcessorClass")
.define_constructor(Constructor<Processor>())
.define_method("create", &Processor::createBigObjects)
.define_method("sum", &Processor::sumBigObjects)
.define_method("sum_const", &Processor::sumBigObjectsConst);

size_t size = 2;
Data_Object<Processor> processor = ProcessorClass.call("new");
Data_Object<BigObject> bigObjects = processor.call("create", size);

Object result = processor.call("sum", bigObjects, size);
ASSERT_EQUAL(11, detail::From_Ruby<int>().convert(result));

result = processor.call("sum_const", bigObjects, size);
ASSERT_EQUAL(11, detail::From_Ruby<int>().convert(result));
}

namespace
{
class SomeClass
Expand Down

0 comments on commit f2b1772

Please sign in to comment.