diff --git a/docs/architecture/overload_resolution.md b/docs/architecture/overload_resolution.md index aca9de64..bf1721c5 100644 --- a/docs/architecture/overload_resolution.md +++ b/docs/architecture/overload_resolution.md @@ -1,6 +1,6 @@ # Overload Resolution -This document explains how Rice resolves C++ method overloads when called from Ruby. +This document explains how Rice resolves C++ method and constructor overloads when called from Ruby. ## Overview @@ -19,6 +19,7 @@ struct Convertible static constexpr double SignedToUnsigned = 0.5;// Penalty for signed to unsigned (can't represent negatives) static constexpr double FloatToInt = 0.5; // Penalty for float to int conversion static constexpr double ConstMismatch = 0.99; // Penalty for const mismatch + static constexpr double RValueMismatch = 0.98; // Prefer borrowing wrapped objects over moving from them }; ``` @@ -194,11 +195,48 @@ When a Ruby-wrapped C++ object is passed as an argument: - Passing a non-const object to a const parameter: Score *= 0.99 (small penalty) - Matching constness: No penalty +## Rvalue References For Wrapped Objects + +Rice distinguishes between: + +- Ruby values that are converted into temporary C++ objects during argument conversion +- Ruby objects that already wrap a live C++ object (`RUBY_T_DATA`) + +For wrapped objects, Rice prefers borrowing overloads over rvalue-reference overloads: + +- Passing a wrapped object to `T&`: Score = 1.0 when constness matches +- Passing a wrapped object to `const T&`: Score = 0.99 when the wrapped object is non-const +- Passing a wrapped object to `T&&`: Score = 0.98 + +This rule exists because moving from an already wrapped C++ object is usually surprising from Ruby. The +Ruby object remains live after the call, so choosing `T&&` over `const T&` can leave the original object in +a moved-from state. + +Example: + +```cpp +Consumer(const Source&); +Consumer(Source&&); +``` + +Called from Ruby as: + +```ruby +source = Source.new +consumer = Consumer.new(source) +``` + +Rice will prefer `Consumer(const Source&)` over `Consumer(Source&&)` because `source` already wraps a live +C++ object and should be borrowed rather than consumed. + +This penalty only applies to wrapped Ruby objects. If there is no borrowing overload available, `T&&` +overloads remain callable, which allows move-only constructor and method parameters to continue working. + ## Resolution Process The resolution happens in `rice/detail/Native.ipp`: -1. `Native::resolve()` is called when Ruby invokes a method +1. `Native::resolve()` is called when Ruby invokes a method or constructor 2. For each registered overload, `Native::matches()` computes a score 3. Overloads are sorted by score (highest first) 4. The highest-scoring overload is selected diff --git a/include/rice/rice.hpp b/include/rice/rice.hpp index 904e01ae..cd072d03 100644 --- a/include/rice/rice.hpp +++ b/include/rice/rice.hpp @@ -189,6 +189,9 @@ namespace Rice constexpr bool is_ostreamable_v = is_ostreamable::value; // Is the type comparable? + // Libraries with unconstrained operator== declarations may specialize this + // trait to false when equality is not actually usable by Rice's STL + // wrappers. template struct is_comparable : std::false_type {}; @@ -1591,6 +1594,7 @@ namespace Rice::detail static constexpr double SignedToUnsigned = 0.5;// Penalty for signed to unsigned (can't represent negatives) static constexpr double FloatToInt = 0.5; // Domain change penalty when converting float to int (lossy) static constexpr double ConstMismatch = 0.99; // Penalty for const mismatch + static constexpr double RValueMismatch = 0.98; // Prefer borrowing wrapped objects over moving from them }; } @@ -2033,7 +2037,7 @@ namespace Rice * \endcode */ template - Object call(Identifier id, Parameter_Ts... args) const; + Object call(Identifier id, Parameter_Ts&&... args) const; //! Call the Ruby method specified by 'id' on object 'obj'. /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to @@ -2056,7 +2060,7 @@ namespace Rice * \endcode */ template - Object call_kw(Identifier id, Parameter_Ts... args) const; + Object call_kw(Identifier id, Parameter_Ts&&... args) const; //! Vectorized call. /*! Calls the method identified by id with the list of arguments @@ -4279,7 +4283,7 @@ namespace Rice public: Reference(); - Reference(T& data); + Reference(const T& data); Reference(VALUE value); T& get(); @@ -4288,7 +4292,7 @@ namespace Rice }; // Specialization needed when VALUE type matches T, causing constructor ambiguity - // between Reference(T&) and Reference(VALUE). VALUE is unsigned long when + // between Reference(const T&) and Reference(VALUE). VALUE is unsigned long when // SIZEOF_LONG == SIZEOF_VOIDP (Linux/macOS) and unsigned long long when // SIZEOF_LONG_LONG == SIZEOF_VOIDP (Windows x64). #if SIZEOF_LONG == SIZEOF_VOIDP @@ -4460,6 +4464,12 @@ namespace Rice::detail { result = Convertible::None; } + // Existing wrapped Ruby objects should prefer borrowing overloads to + // rvalue-reference overloads so they are not silently moved-from. + else if constexpr (std::is_rvalue_reference_v) + { + result = Convertible::RValueMismatch; + } // It is ok to send a non-const value to a const parameter but // prefer non-const to non-const by slightly decreasing the score else if (!isConst && is_const_any_v) @@ -4496,6 +4506,11 @@ namespace Rice::detail { return this->fromRuby_.convert(valueOpt.value()); } + else if constexpr (std::is_rvalue_reference_v) + { + // Rvalue-reference parameters cannot safely use stored default values. + // Materializing them from std::any would require moving from shared state. + } // Remember std::is_copy_constructible_v>>> returns true. Sigh. // So special case vector handling else if constexpr (detail::is_std_vector_v>) @@ -6460,6 +6475,25 @@ namespace Rice Arg* arg_ = nullptr; }; + template<> + class To_Ruby + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + { + } + + VALUE convert(const char* data) + { + return To_Ruby(arg_).convert(data); + } + + private: + Arg* arg_ = nullptr; + }; + template class To_Ruby { @@ -6492,6 +6526,25 @@ namespace Rice Arg* arg_ = nullptr; }; + template + class To_Ruby + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + { + } + + VALUE convert(const char (&buffer)[N]) + { + return To_Ruby(arg_).convert(buffer); + } + + private: + Arg* arg_ = nullptr; + }; + // =========== unsigned char ============ template<> class To_Ruby @@ -7213,6 +7266,25 @@ namespace Rice Arg* arg_ = nullptr; }; + template<> + class To_Ruby + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + { + } + + VALUE convert(std::nullptr_t const) + { + return Qnil; + } + + private: + Arg* arg_ = nullptr; + }; + // =========== void ============ template<> class To_Ruby @@ -7271,6 +7343,7 @@ namespace Rice }; } } + // ========= from_ruby.ipp ========= #include #include @@ -9051,7 +9124,7 @@ namespace Rice } template - inline Reference::Reference(T& data) : data_(data) + inline Reference::Reference(const T& data) : data_(data) { } @@ -12664,6 +12737,25 @@ namespace Rice::detail } }; + // Wraps a C++ function as a Ruby proc + template + class To_Ruby + { + public: + using Proc_T = Return_T(*&)(Parameter_Ts...); + + To_Ruby() = default; + + explicit To_Ruby(Arg*) + {} + + VALUE convert(Proc_T proc) + { + // Wrap the C+++ function pointer as a Ruby Proc + return NativeProc::createRubyProc(std::forward(proc)); + } + }; + // Makes a Ruby proc callable as C callback template class From_Ruby @@ -12716,53 +12808,6 @@ namespace Rice } } -/*namespace Rice::detail -{ - template<> - struct Type - { - static bool verify() - { - return true; - } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(const Encoding& encoding) - { - // return x.value(); - } - }; - - template<> - class From_Ruby - { - public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_SYMBOL: - return Convertible::Exact; - break; - case RUBY_T_STRING: - return Convertible::Cast; - break; - default: - return Convertible::None; - } - } - - Encoding convert(VALUE value) - { - // return Symbol(value); - } - }; -} -*/ // ========= Object.ipp ========= namespace Rice { @@ -12808,7 +12853,7 @@ namespace Rice } template - inline Object Object::call(Identifier id, Parameter_Ts... args) const + inline Object Object::call(Identifier id, Parameter_Ts&&... args) const { /* IMPORTANT - We store VALUEs in an array that is a local variable. That allows the Ruby garbage collector to find them when scanning @@ -12822,10 +12867,10 @@ namespace Rice } template - inline Object Object::call_kw(Identifier id, Parameter_Ts... args) const + inline Object Object::call_kw(Identifier id, Parameter_Ts&&... args) const { /* IMPORTANT - See call() above */ - std::array values = { detail::To_Ruby>().convert(args)... }; + std::array values = { detail::To_Ruby>().convert(std::forward(args))... }; return detail::protect(rb_funcallv_kw, this->validated_value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_KEYWORDS); } @@ -13173,6 +13218,25 @@ namespace Rice::detail return x.value(); } + private: + Arg* arg_ = nullptr; + }; + + template<> + class To_Ruby + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + { + } + + VALUE convert(String const& x) + { + return x.value(); + } + private: Arg* arg_ = nullptr; }; @@ -13906,6 +13970,25 @@ namespace Rice::detail Arg* arg_ = nullptr; }; + template<> + class To_Ruby + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + { + } + + VALUE convert(Hash const& x) + { + return x.value(); + } + + private: + Arg* arg_ = nullptr; + }; + template<> class From_Ruby { @@ -15120,7 +15203,7 @@ namespace Rice static void initialize(VALUE self, Parameter_Ts...args) { // Call C++ constructor - T* data = new T(args...); + T* data = new T(std::forward(args)...); detail::wrapConstructed(self, Data_Type::ruby_data_type(), data); } @@ -15154,11 +15237,12 @@ namespace Rice static void initialize(Object self, Parameter_Ts...args) { // Call C++ constructor - T* data = new T(self, args...); + T* data = new T(self, std::forward(args)...); detail::wrapConstructed(self.value(), Data_Type::ruby_data_type(), data); } }; } + // ========= Callback.hpp ========= namespace Rice @@ -15521,16 +15605,6 @@ namespace Rice::detail Arg* arg_ = nullptr; }; - template - class To_Ruby> - { - public: - VALUE convert(const Object& x) - { - return x.value(); - } - }; - template class From_Ruby { @@ -15876,38 +15950,6 @@ namespace Rice::detail Arg* arg_ = nullptr; std::vector vector_; }; - - template - class From_Ruby> - { - static_assert(!std::is_fundamental_v>, - "Data_Object cannot be used with fundamental types"); - - static_assert(!std::is_same_v> && !std::is_same_v> && - !std::is_same_v && !std::is_same_v> && - !std::is_same_v> && !std::is_same_v> && - !std::is_same_v> && !std::is_same_v && - !std::is_same_v>, - "Please include rice/stl.hpp header for STL support"); - - public: - double is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_DATA: - return Data_Type::is_descendant(value) ? Convertible::Exact : Convertible::None; - break; - default: - return Convertible::None; - } - } - - static Data_Object convert(VALUE value) - { - return Data_Object(value); - } - }; } // ========= Enum.hpp ========= diff --git a/include/rice/stl.hpp b/include/rice/stl.hpp index 102226f2..58f9d18b 100644 --- a/include/rice/stl.hpp +++ b/include/rice/stl.hpp @@ -151,9 +151,9 @@ namespace Rice::stl klass_.define_method("initialize", [](VALUE self, VALUE callable) -> void { // Create std::function that wraps the Ruby callable - Function_T* data = new Function_T([callable](auto... args) + Function_T* data = new Function_T([callable](auto&&... args) { - Object result = Object(callable).call("call", args...); + Object result = Object(callable).call("call", std::forward(args)...); using Return_T = typename Function_T::result_type; if constexpr (!std::is_void_v) @@ -211,6 +211,146 @@ namespace Rice namespace Rice::detail { + template + inline std::function makeRubyFunction(VALUE value) + { + Pin proc(value); + + return [proc = std::move(proc)](Parameter_Ts... args) -> Return_T + { + Object result = Object(proc.value()).call("call", std::forward(args)...); + + if constexpr (!std::is_void_v) + { + return From_Ruby>().convert(result); + } + }; + } + + template + class From_Ruby> + { + public: + using Function_T = std::function; + + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } + + double is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_DATA: + return Data_Type::is_descendant(value) ? Convertible::Exact : Convertible::None; + default: + return protect(rb_obj_is_proc, value) == Qtrue ? Convertible::ConstMismatch : Convertible::None; + } + } + + Function_T convert(VALUE value) + { + if (Data_Type::is_descendant(value)) + { + return *detail::unwrap(value, Data_Type::ruby_data_type(), false); + } + else if (protect(rb_obj_is_proc, value) == Qtrue) + { + return makeRubyFunction(value); + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + detail::protect(rb_obj_classname, value), "std::function"); + } + } + + private: + Arg* arg_ = nullptr; + }; + + template + class From_Ruby&> + { + public: + using Function_T = std::function; + + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } + + double is_convertible(VALUE value) + { + return From_Ruby(arg_).is_convertible(value); + } + + Function_T& convert(VALUE value) + { + if (Data_Type::is_descendant(value)) + { + return *detail::unwrap(value, Data_Type::ruby_data_type(), false); + } + else if (protect(rb_obj_is_proc, value) == Qtrue) + { + converted_ = makeRubyFunction(value); + return converted_; + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + detail::protect(rb_obj_classname, value), "std::function"); + } + } + + private: + Arg* arg_ = nullptr; + Function_T converted_; + }; + + template + class From_Ruby&&> + { + public: + using Function_T = std::function; + + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } + + double is_convertible(VALUE value) + { + return From_Ruby(arg_).is_convertible(value); + } + + Function_T&& convert(VALUE value) + { + if (Data_Type::is_descendant(value)) + { + return std::move(*detail::unwrap(value, Data_Type::ruby_data_type(), false)); + } + else if (protect(rb_obj_is_proc, value) == Qtrue) + { + converted_ = makeRubyFunction(value); + return std::move(converted_); + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + detail::protect(rb_obj_classname, value), "std::function"); + } + } + + private: + Arg* arg_ = nullptr; + Function_T converted_; + }; + template struct Type> { @@ -867,6 +1007,24 @@ namespace Rice::detail Arg* arg_ = nullptr; }; + template<> + class To_Ruby + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + {} + + VALUE convert(const std::nullopt_t&) + { + return Qnil; + } + + private: + Arg* arg_ = nullptr; + }; + template class To_Ruby> { @@ -1463,6 +1621,24 @@ namespace Rice::detail Arg* arg_ = nullptr; }; + template + class To_Ruby&> + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + {} + + VALUE convert(const std::reference_wrapper& data) + { + return To_Ruby().convert(data.get()); + } + + private: + Arg* arg_ = nullptr; + }; + template class From_Ruby> { @@ -1626,16 +1802,8 @@ namespace Rice return it != map.end(); }, Arg("value")); rb_define_alias(klass_, "eql?", "=="); + rb_define_alias(klass_, "has_value", "value?"); } - else - { - klass_.define_method("value?", [](T&, Mapped_Parameter_T) -> bool - { - return false; - }, Arg("value")); - } - - rb_define_alias(klass_, "has_value", "value?"); } void define_modify_methods() @@ -1969,6 +2137,7 @@ namespace Rice } } + // ========= monostate.hpp ========= @@ -2230,16 +2399,8 @@ namespace Rice return it != multimap.end(); }, Arg("value")); rb_define_alias(klass_, "eql?", "=="); + rb_define_alias(klass_, "has_value", "value?"); } - else - { - klass_.define_method("value?", [](T&, Mapped_Parameter_T) -> bool - { - return false; - }, Arg("value")); - } - - rb_define_alias(klass_, "has_value", "value?"); } void define_modify_methods() @@ -2554,6 +2715,7 @@ namespace Rice } } + // ========= set.hpp ========= namespace Rice @@ -2667,23 +2829,23 @@ namespace Rice void define_operators() { - klass_ - .define_method("<<", [](T& self, Parameter_T value) -> T& - { - self.insert(value); - return self; - }, Arg("value").keepAlive()) - .define_method("==", [](const T& self, const T& other) -> bool + klass_.define_method("<<", [](T& self, Parameter_T value) -> T& + { + self.insert(value); + return self; + }, Arg("value").keepAlive()); + + if constexpr (detail::is_comparable_v) + { + klass_.define_method("==", [](const T& self, const T& other) -> bool { - if constexpr (detail::is_comparable_v) - { - return self == other; - } - else - { - return false; - } - }, Arg("other")) + return self == other; + }, Arg("other")); + + rb_define_alias(klass_, "eql?", "=="); + } + + klass_ .define_method("&", [](const T& self, const T& other) -> T { T result; @@ -2730,15 +2892,14 @@ namespace Rice return std::includes(self.begin(), self.end(), other.begin(), other.end()); }, Arg("other")); - - rb_define_alias(klass_, "eql?", "=="); - rb_define_alias(klass_, "intersection", "&"); - rb_define_alias(klass_, "union", "|"); - rb_define_alias(klass_, "difference", "-"); - rb_define_alias(klass_, "proper_subset?", "<"); - rb_define_alias(klass_, "subset?", "<"); - rb_define_alias(klass_, "proper_superset?", ">"); - rb_define_alias(klass_, "superset?", ">"); + + rb_define_alias(klass_, "intersection", "&"); + rb_define_alias(klass_, "union", "|"); + rb_define_alias(klass_, "difference", "-"); + rb_define_alias(klass_, "proper_subset?", "<"); + rb_define_alias(klass_, "subset?", "<"); + rb_define_alias(klass_, "proper_superset?", ">"); + rb_define_alias(klass_, "superset?", ">"); } void define_enumerable() @@ -3865,6 +4026,9 @@ namespace Rice template Data_Type> define_unique_ptr(std::string klassName) { + static_assert(detail::is_complete_v, + "Rice does not support binding std::unique_ptr when T is incomplete."); + using UniquePtr_T = std::unique_ptr; using Data_Type_T = Data_Type; @@ -4151,16 +4315,8 @@ namespace Rice return it != unordered_map.end(); }, Arg("value")); rb_define_alias(klass_, "eql?", "=="); + rb_define_alias(klass_, "has_value", "value?"); } - else - { - klass_.define_method("value?", [](T&, Mapped_Parameter_T) -> bool - { - return false; - }, Arg("value")); - } - - rb_define_alias(klass_, "has_value", "value?"); } void define_modify_methods() @@ -4494,6 +4650,7 @@ namespace Rice } } + // ========= vector.hpp ========= namespace Rice @@ -4802,21 +4959,6 @@ namespace Rice }, Arg("value")); rb_define_alias(klass_, "eql?", "=="); } - else - { - klass_.define_method("delete", [](T&, Parameter_T) -> std::optional - { - return std::nullopt; - }, Arg("value")) - .define_method("include?", [](const T&, Parameter_T) - { - return false; - }, Arg("value")) - .define_method("index", [](const T&, Parameter_T) -> std::optional - { - return std::nullopt; - }, Arg("value")); - } } void define_modify_methods() @@ -5227,6 +5369,24 @@ namespace Rice private: Arg* arg_ = nullptr; }; + + template<> + class To_Ruby::reference&> + { + public: + To_Ruby() = default; + + explicit To_Ruby(Arg* arg) : arg_(arg) + {} + + VALUE convert(const std::vector::reference& value) + { + return value ? Qtrue : Qfalse; + } + + private: + Arg* arg_ = nullptr; + }; } } diff --git a/rice/detail/Parameter.ipp b/rice/detail/Parameter.ipp index d1ba42a5..371f4edf 100644 --- a/rice/detail/Parameter.ipp +++ b/rice/detail/Parameter.ipp @@ -54,6 +54,12 @@ namespace Rice::detail { result = Convertible::None; } + // Existing wrapped Ruby objects should prefer borrowing overloads to + // rvalue-reference overloads so they are not silently moved-from. + else if constexpr (std::is_rvalue_reference_v) + { + result = Convertible::RValueMismatch; + } // It is ok to send a non-const value to a const parameter but // prefer non-const to non-const by slightly decreasing the score else if (!isConst && is_const_any_v) @@ -90,6 +96,11 @@ namespace Rice::detail { return this->fromRuby_.convert(valueOpt.value()); } + else if constexpr (std::is_rvalue_reference_v) + { + // Rvalue-reference parameters cannot safely use stored default values. + // Materializing them from std::any would require moving from shared state. + } // Remember std::is_copy_constructible_v>>> returns true. Sigh. // So special case vector handling else if constexpr (detail::is_std_vector_v>) diff --git a/rice/detail/from_ruby.hpp b/rice/detail/from_ruby.hpp index d6c6aee9..fada99ce 100644 --- a/rice/detail/from_ruby.hpp +++ b/rice/detail/from_ruby.hpp @@ -39,6 +39,7 @@ namespace Rice::detail static constexpr double SignedToUnsigned = 0.5;// Penalty for signed to unsigned (can't represent negatives) static constexpr double FloatToInt = 0.5; // Domain change penalty when converting float to int (lossy) static constexpr double ConstMismatch = 0.99; // Penalty for const mismatch + static constexpr double RValueMismatch = 0.98; // Prefer borrowing wrapped objects over moving from them }; } diff --git a/test/test_Overloads.cpp b/test/test_Overloads.cpp index 45ad2c09..6477de97 100644 --- a/test/test_Overloads.cpp +++ b/test/test_Overloads.cpp @@ -17,6 +17,8 @@ namespace class MyClass6; class MyClass7; class MyClass8; + class MyClass9; + class MyClass10; } SETUP(Overloads) @@ -31,6 +33,8 @@ SETUP(Overloads) Data_Type::unbind(); Data_Type::unbind(); Data_Type::unbind(); + Data_Type::unbind(); + Data_Type::unbind(); Rice::detail::Registries::instance.types.remove(); Rice::detail::Registries::instance.types.remove(); Rice::detail::Registries::instance.types.remove(); @@ -39,6 +43,8 @@ SETUP(Overloads) Rice::detail::Registries::instance.types.remove(); Rice::detail::Registries::instance.types.remove(); Rice::detail::Registries::instance.types.remove(); + Rice::detail::Registries::instance.types.remove(); + Rice::detail::Registries::instance.types.remove(); Rice::detail::Registries::instance.natives.reset(Module(rb_mKernel)); } @@ -826,6 +832,73 @@ TESTCASE(ConstRef) ASSERT_EQUAL("const ref", result.str()); } +namespace +{ + class MyClass9 + { + public: + MyClass9() = default; + MyClass9(const MyClass9&) = default; + + MyClass9(MyClass9&& other) noexcept + : moved_from_(other.moved_from_) + { + other.moved_from_ = true; + } + + bool movedFrom() const + { + return moved_from_; + } + + private: + bool moved_from_ = false; + }; + + class MyClass10 + { + public: + MyClass10(const MyClass9&) + { + this->result = "const ref"; + } + + MyClass10(MyClass9&& value) + : value_(std::move(value)) + { + this->result = "rvalue ref"; + } + + std::string result = ""; + + private: + MyClass9 value_; + }; +} + +TESTCASE(ConstructorRValueRefShouldNotBeatConstRefForWrappedObjects) +{ + Class c9 = define_class("MyClass9") + .define_constructor(Constructor()) + .define_method("moved_from?", &MyClass9::movedFrom); + + Class c10 = define_class("MyClass10") + .define_constructor(Constructor()) + .define_constructor(Constructor()) + .define_attr("result", &MyClass10::result); + + Module m = define_module("Testing"); + + Array result = m.module_eval(R"( + source = MyClass9.new + consumer = MyClass10.new(source) + [consumer.result, source.moved_from?] + )"); + + ASSERT_EQUAL("const ref", String(result[0].value()).str()); + ASSERT_EQUAL(Qfalse, result[1].value()); +} + namespace { class MyClass6 @@ -1255,4 +1328,4 @@ TESTCASE(MethodBlock) String result = m.module_eval(code); ASSERT_EQUAL("Method Block", result.c_str()); -} \ No newline at end of file +} diff --git a/test/test_Stl_Vector.cpp b/test/test_Stl_Vector.cpp index fdf85698..6598772c 100644 --- a/test/test_Stl_Vector.cpp +++ b/test/test_Stl_Vector.cpp @@ -31,6 +31,28 @@ namespace return result; } }; + + class VectorRValueSink + { + public: + explicit VectorRValueSink(std::vector&& values) + : values_(std::move(values)) + { + } + + void setValues(std::vector&& values) + { + values_ = std::move(values); + } + + size_t size() const + { + return values_.size(); + } + + private: + std::vector values_; + }; } Class makeVectorClass() @@ -83,6 +105,25 @@ TESTCASE(StringVectorData) ASSERT_EQUAL("World", detail::From_Ruby().convert(array[1].value()).c_str()); } +TESTCASE(VectorRvalueParameters) +{ + Module m(anonymous_module()); + define_vector("IntVector"); + + define_class_under(m, "VectorRValueSink") + .define_constructor(Constructor&&>(), Arg("values")) + .define_method("set_values", &VectorRValueSink::setValues, Arg("values")) + .define_method("size", &VectorRValueSink::size); + + Object result = m.module_eval(R"( + sink = VectorRValueSink.new(Std::IntVector.new([1, 2, 3])) + sink.set_values(Std::IntVector.new([4, 5])) + sink.size + )"); + + ASSERT_EQUAL(2, detail::From_Ruby().convert(result)); +} + TESTCASE(VectorBoolReferenceLvalueToRuby) { std::vector values{ true };