rice 4.11.2 → 4.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbea336b28656bc1123b687fe1649c58c5c497cc01efafeabe7efdac71577d46
4
- data.tar.gz: 4910c437a1e878c3c0dc534828ee89b499cf14a3d3ab4011ead2944f6063cca4
3
+ metadata.gz: 2b52ff5ddc64209e1c252d529111695d833a57d1362e15b6d223777194dc6750
4
+ data.tar.gz: 214c2b675683e68d026440cc83499a7de0faa6282563c0bfa028c925e677f195
5
5
  SHA512:
6
- metadata.gz: 9f8f3dc946883c516c3e3e79ea11866503cd059d95765586789490c31fbd0881ed6d5fb905fd9f60120df3f2d4980a2a50bb8005d245e17feb94f85d217bbf3a
7
- data.tar.gz: 2b69b75163ca48b82f5b11041094885d64b5c18abe0b7306912e96a94f5f986afeb968127fc7b6b3d513ee6297160adc7bd0008d1cbdfc669d0660e3aa7e8d2c
6
+ metadata.gz: fdee833532115137ec7e375e83e377c5e21a634b44e0b67bb5954ea3ad04a2983ef3c6ffc5d5deb804b1d94c7b76f04d2cc94b73b054aaa0722f01fd39f58c37
7
+ data.tar.gz: 23e9990bbf398a067608c29028018840506254f3a8a00a7f6cc56840ec88e70007d0309656b297b20ab31776805b3deccb14e16fface820b0ec7d3340aafc0f3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.11.3 (2026-03-11)
4
+
5
+ ### Bug Fixes
6
+
7
+ * Fix C++20 compilation error with `std::ostringstream::str` overload (#395)
8
+ * Add `function_traits` specializations for ref-qualified member functions (`&`, `const&`)
9
+
10
+ ### Enhancements
11
+
12
+ * Support C++20 and C++23 compilation
13
+ * Make C++ standard version configurable via `--with-cxx-standard` in mkmf-rice
14
+ * Add `/Zc:__cplusplus` to MSVC build flags
15
+ * Add CMake-based CI job testing C++17, C++20, and C++23
16
+ * Add test case for function pointer struct attributes
17
+ * Add support for `std::shared_ptr<const T>`
18
+ * Add support for `std::unique_ptr<const T>`
19
+ * Add support for non-constructible objects in std::pair, std::map, std::multimap
20
+ * Add support for wrapping pointers to abstract types that cannot be created nor destroyed
21
+ * Fix converting std::nullptr_t to Ruby
22
+
3
23
  ## 4.11.2 (2026-02-21)
4
24
 
5
25
  ### Enhancements
data/CMakePresets.json CHANGED
@@ -116,7 +116,7 @@
116
116
  "toolchainFile": "$env{VCPKG_ROOT}\\scripts\\buildsystems\\vcpkg.cmake",
117
117
  "cacheVariables": {
118
118
  "CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
119
- "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE"
119
+ "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /Zc:__cplusplus /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE"
120
120
  },
121
121
  "condition": {
122
122
  "type": "equals",
@@ -155,7 +155,7 @@
155
155
  "cacheVariables": {
156
156
  "CMAKE_BUILD_TYPE": "Debug",
157
157
  "CMAKE_CXX_COMPILER": "clang-cl.exe",
158
- "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field",
158
+ "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /Zc:__cplusplus /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field",
159
159
  "CMAKE_CXX_FLAGS_DEBUG": "/Od /Zi"
160
160
  }
161
161
  },
@@ -166,7 +166,7 @@
166
166
  "cacheVariables": {
167
167
  "CMAKE_BUILD_TYPE": "Release",
168
168
  "CMAKE_CXX_COMPILER": "clang-cl.exe",
169
- "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field",
169
+ "CMAKE_CXX_FLAGS": "/EHs /W4 /bigobj /utf-8 /Zc:__cplusplus /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /clang:-Wno-unused-private-field",
170
170
  "CMAKE_CXX_FLAGS_RELEASE": "/O2 /DNDEBUG",
171
171
  "CMAKE_INTERPROCEDURAL_OPTIMIZATION": "ON"
172
172
  }
@@ -556,6 +556,27 @@ namespace Rice::detail
556
556
  {
557
557
  };
558
558
 
559
+ // ref-qualified member Functions on C++ classes (C++20 uses these for std library types)
560
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
561
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) &> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
562
+ {
563
+ };
564
+
565
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
566
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) const&> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
567
+ {
568
+ };
569
+
570
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
571
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) & noexcept> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
572
+ {
573
+ };
574
+
575
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
576
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) const& noexcept> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
577
+ {
578
+ };
579
+
559
580
  /*// Functors and lambdas
560
581
  template<class Function_T>
561
582
  struct function_traits<Function_T&> : public function_traits<Function_T>
@@ -8854,31 +8875,15 @@ namespace Rice::detail
8854
8875
  }
8855
8876
  }
8856
8877
 
8857
- void* convert(VALUE value)
8878
+ std::nullptr_t convert(VALUE value)
8858
8879
  {
8859
8880
  if (value == Qnil)
8860
8881
  {
8861
8882
  return nullptr;
8862
8883
  }
8863
8884
 
8864
- if (this->arg_ && this->arg_->isOpaque())
8865
- {
8866
- return (void*)value;
8867
- }
8868
-
8869
- switch (rb_type(value))
8870
- {
8871
- case RUBY_T_NIL:
8872
- {
8873
- return nullptr;
8874
- break;
8875
- }
8876
- default:
8877
- {
8878
- throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
8879
- detail::protect(rb_obj_classname, value), "nil");
8880
- }
8881
- }
8885
+ throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
8886
+ detail::protect(rb_obj_classname, value), "nil");
8882
8887
  }
8883
8888
  private:
8884
8889
  Arg* arg_ = nullptr;
@@ -10277,7 +10282,9 @@ namespace Rice::detail
10277
10282
 
10278
10283
  if constexpr (is_complete_v<T>)
10279
10284
  {
10280
- if constexpr (std::is_destructible_v<T>)
10285
+ // is_abstract_v requires a complete type, so nest inside is_complete_v.
10286
+ // Deleting an abstract class through a non-virtual destructor is UB.
10287
+ if constexpr (std::is_destructible_v<T> && !std::is_abstract_v<T>)
10281
10288
  {
10282
10289
  if (this->isOwner_)
10283
10290
  {
data/include/rice/stl.hpp CHANGED
@@ -1133,8 +1133,12 @@ namespace Rice::stl
1133
1133
 
1134
1134
  void define_methods()
1135
1135
  {
1136
+ #if __cplusplus >= 202002L
1137
+ klass_.define_method<std::string(std::ostringstream::*)() const&>("str", &std::ostringstream::str)
1138
+ #else
1136
1139
  klass_.define_method<std::string(std::ostringstream::*)() const>("str", &std::ostringstream::str)
1137
- .define_method("str=", [](std::ostringstream& stream, const std::string& s) { stream.str(s); }, Arg("str"));
1140
+ #endif
1141
+ .define_method("str=", [](std::ostringstream& stream, const std::string& s) { stream.str(s); }, Arg("str"));
1138
1142
 
1139
1143
  rb_define_alias(klass_, "to_s", "str");
1140
1144
  }
@@ -1310,8 +1314,12 @@ namespace Rice
1310
1314
  private:
1311
1315
  void define_constructors()
1312
1316
  {
1313
- klass_.define_constructor(Constructor<T>())
1314
- .define_constructor(Constructor<T, First_Parameter_T, Second_Parameter_T>(), Arg("x").keepAlive(), Arg("y").keepAlive());
1317
+ if constexpr (std::is_default_constructible_v<T>)
1318
+ {
1319
+ klass_.define_constructor(Constructor<T>());
1320
+ }
1321
+
1322
+ klass_.define_constructor(Constructor<T, First_Parameter_T, Second_Parameter_T>(), Arg("x").keepAlive(), Arg("y").keepAlive());
1315
1323
 
1316
1324
  if constexpr (std::is_copy_constructible_v<First_T> && std::is_copy_constructible_v<Second_T>)
1317
1325
  {
@@ -1650,7 +1658,7 @@ namespace Rice
1650
1658
  }, Arg("key"))
1651
1659
  .define_method("[]=", [](T& map, Key_T key, Mapped_Parameter_T value) -> Mapped_T
1652
1660
  {
1653
- map[key] = value;
1661
+ map.insert_or_assign(key, value);
1654
1662
  return value;
1655
1663
  }, Arg("key").keepAlive(), Arg("value").keepAlive());
1656
1664
 
@@ -1772,7 +1780,7 @@ namespace Rice
1772
1780
  // exceptions propogate back to Ruby
1773
1781
  return cpp_protect([&]
1774
1782
  {
1775
- result->operator[](From_Ruby<T>().convert(key)) = From_Ruby<U>().convert(value);
1783
+ result->insert_or_assign(From_Ruby<T>().convert(key), From_Ruby<U>().convert(value));
1776
1784
  return ST_CONTINUE;
1777
1785
  });
1778
1786
  }
@@ -3201,7 +3209,11 @@ namespace Rice
3201
3209
 
3202
3210
  if constexpr (detail::is_complete_v<T> && !std::is_void_v<T>)
3203
3211
  {
3204
- result.define_constructor(Constructor<SharedPtr_T, typename SharedPtr_T::element_type*>(), Arg("value").takeOwnership());
3212
+ // is_abstract_v requires a complete type, so it must be nested inside the is_complete_v check
3213
+ if constexpr (!std::is_abstract_v<T>)
3214
+ {
3215
+ result.define_constructor(Constructor<SharedPtr_T, typename SharedPtr_T::element_type*>(), Arg("value").takeOwnership());
3216
+ }
3205
3217
  }
3206
3218
 
3207
3219
  // Forward methods to wrapped T
@@ -3256,7 +3268,7 @@ namespace Rice::detail
3256
3268
  }
3257
3269
  else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType))
3258
3270
  {
3259
- return this->data_.get();
3271
+ return (void*)this->data_.get();
3260
3272
  }
3261
3273
  else
3262
3274
  {
@@ -3931,7 +3943,7 @@ namespace Rice::detail
3931
3943
  }
3932
3944
  else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType))
3933
3945
  {
3934
- return this->data_.get();
3946
+ return (void*)this->data_.get();
3935
3947
  }
3936
3948
  else
3937
3949
  {
@@ -4171,7 +4183,7 @@ namespace Rice
4171
4183
  }, Arg("key"))
4172
4184
  .define_method("[]=", [](T& unordered_map, Key_T key, Mapped_Parameter_T value) -> Mapped_T
4173
4185
  {
4174
- unordered_map[key] = value;
4186
+ unordered_map.insert_or_assign(key, value);
4175
4187
  return value;
4176
4188
  }, Arg("key").keepAlive(), Arg("value").keepAlive());
4177
4189
 
@@ -4293,7 +4305,7 @@ namespace Rice
4293
4305
  // exceptions propogate back to Ruby
4294
4306
  return cpp_protect([&]
4295
4307
  {
4296
- result->operator[](From_Ruby<T>().convert(key)) = From_Ruby<U>().convert(value);
4308
+ result->insert_or_assign(From_Ruby<T>().convert(key), From_Ruby<U>().convert(value));
4297
4309
  return ST_CONTINUE;
4298
4310
  });
4299
4311
  }
data/lib/mkmf-rice.rb CHANGED
@@ -24,14 +24,16 @@ end
24
24
  # Now pull in the C++ support
25
25
  include MakeMakefile['C++']
26
26
 
27
- # Rice needs c++17.
27
+ # Rice needs c++17 or higher. Use --with-cxx-standard=20 to override.
28
+ std = with_config('cxx-standard', '17')
29
+
28
30
  if IS_MSWIN
29
- $CXXFLAGS += " /std:c++17 /EHs /permissive- /bigobj /utf-8"
31
+ $CXXFLAGS += " /std:c++#{std} /EHs /permissive- /bigobj /utf-8 /Zc:__cplusplus"
30
32
  $CPPFLAGS += " -D_ALLOW_KEYWORD_MACROS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE"
31
33
  elsif IS_MINGW
32
- $CXXFLAGS += " -std=c++17 -Wa,-mbig-obj"
34
+ $CXXFLAGS += " -std=c++#{std} -Wa,-mbig-obj"
33
35
  else
34
- $CXXFLAGS += " -std=c++17"
36
+ $CXXFLAGS += " -std=c++#{std}"
35
37
  end
36
38
 
37
39
  # Rice needs to include its header. Let's setup the include path
data/lib/rice/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rice
2
- VERSION = "4.11.2"
2
+ VERSION = "4.11.3"
3
3
  end
@@ -121,7 +121,9 @@ namespace Rice::detail
121
121
 
122
122
  if constexpr (is_complete_v<T>)
123
123
  {
124
- if constexpr (std::is_destructible_v<T>)
124
+ // is_abstract_v requires a complete type, so nest inside is_complete_v.
125
+ // Deleting an abstract class through a non-virtual destructor is UB.
126
+ if constexpr (std::is_destructible_v<T> && !std::is_abstract_v<T>)
125
127
  {
126
128
  if (this->isOwner_)
127
129
  {
@@ -1608,31 +1608,15 @@ namespace Rice::detail
1608
1608
  }
1609
1609
  }
1610
1610
 
1611
- void* convert(VALUE value)
1611
+ std::nullptr_t convert(VALUE value)
1612
1612
  {
1613
1613
  if (value == Qnil)
1614
1614
  {
1615
1615
  return nullptr;
1616
1616
  }
1617
1617
 
1618
- if (this->arg_ && this->arg_->isOpaque())
1619
- {
1620
- return (void*)value;
1621
- }
1622
-
1623
- switch (rb_type(value))
1624
- {
1625
- case RUBY_T_NIL:
1626
- {
1627
- return nullptr;
1628
- break;
1629
- }
1630
- default:
1631
- {
1632
- throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
1633
- detail::protect(rb_obj_classname, value), "nil");
1634
- }
1635
- }
1618
+ throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
1619
+ detail::protect(rb_obj_classname, value), "nil");
1636
1620
  }
1637
1621
  private:
1638
1622
  Arg* arg_ = nullptr;
data/rice/stl/map.ipp CHANGED
@@ -156,7 +156,7 @@ namespace Rice
156
156
  }, Arg("key"))
157
157
  .define_method("[]=", [](T& map, Key_T key, Mapped_Parameter_T value) -> Mapped_T
158
158
  {
159
- map[key] = value;
159
+ map.insert_or_assign(key, value);
160
160
  return value;
161
161
  }, Arg("key").keepAlive(), Arg("value").keepAlive());
162
162
 
@@ -278,7 +278,7 @@ namespace Rice
278
278
  // exceptions propogate back to Ruby
279
279
  return cpp_protect([&]
280
280
  {
281
- result->operator[](From_Ruby<T>().convert(key)) = From_Ruby<U>().convert(value);
281
+ result->insert_or_assign(From_Ruby<T>().convert(key), From_Ruby<U>().convert(value));
282
282
  return ST_CONTINUE;
283
283
  });
284
284
  }
data/rice/stl/ostream.ipp CHANGED
@@ -59,8 +59,12 @@ namespace Rice::stl
59
59
 
60
60
  void define_methods()
61
61
  {
62
+ #if __cplusplus >= 202002L
63
+ klass_.define_method<std::string(std::ostringstream::*)() const&>("str", &std::ostringstream::str)
64
+ #else
62
65
  klass_.define_method<std::string(std::ostringstream::*)() const>("str", &std::ostringstream::str)
63
- .define_method("str=", [](std::ostringstream& stream, const std::string& s) { stream.str(s); }, Arg("str"));
66
+ #endif
67
+ .define_method("str=", [](std::ostringstream& stream, const std::string& s) { stream.str(s); }, Arg("str"));
64
68
 
65
69
  rb_define_alias(klass_, "to_s", "str");
66
70
  }
data/rice/stl/pair.ipp CHANGED
@@ -24,8 +24,12 @@ namespace Rice
24
24
  private:
25
25
  void define_constructors()
26
26
  {
27
- klass_.define_constructor(Constructor<T>())
28
- .define_constructor(Constructor<T, First_Parameter_T, Second_Parameter_T>(), Arg("x").keepAlive(), Arg("y").keepAlive());
27
+ if constexpr (std::is_default_constructible_v<T>)
28
+ {
29
+ klass_.define_constructor(Constructor<T>());
30
+ }
31
+
32
+ klass_.define_constructor(Constructor<T, First_Parameter_T, Second_Parameter_T>(), Arg("x").keepAlive(), Arg("y").keepAlive());
29
33
 
30
34
  if constexpr (std::is_copy_constructible_v<First_T> && std::is_copy_constructible_v<Second_T>)
31
35
  {
@@ -32,7 +32,11 @@ namespace Rice
32
32
 
33
33
  if constexpr (detail::is_complete_v<T> && !std::is_void_v<T>)
34
34
  {
35
- result.define_constructor(Constructor<SharedPtr_T, typename SharedPtr_T::element_type*>(), Arg("value").takeOwnership());
35
+ // is_abstract_v requires a complete type, so it must be nested inside the is_complete_v check
36
+ if constexpr (!std::is_abstract_v<T>)
37
+ {
38
+ result.define_constructor(Constructor<SharedPtr_T, typename SharedPtr_T::element_type*>(), Arg("value").takeOwnership());
39
+ }
36
40
  }
37
41
 
38
42
  // Forward methods to wrapped T
@@ -87,7 +91,7 @@ namespace Rice::detail
87
91
  }
88
92
  else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType))
89
93
  {
90
- return this->data_.get();
94
+ return (void*)this->data_.get();
91
95
  }
92
96
  else
93
97
  {
@@ -83,7 +83,7 @@ namespace Rice::detail
83
83
  }
84
84
  else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType))
85
85
  {
86
- return this->data_.get();
86
+ return (void*)this->data_.get();
87
87
  }
88
88
  else
89
89
  {
@@ -156,7 +156,7 @@ namespace Rice
156
156
  }, Arg("key"))
157
157
  .define_method("[]=", [](T& unordered_map, Key_T key, Mapped_Parameter_T value) -> Mapped_T
158
158
  {
159
- unordered_map[key] = value;
159
+ unordered_map.insert_or_assign(key, value);
160
160
  return value;
161
161
  }, Arg("key").keepAlive(), Arg("value").keepAlive());
162
162
 
@@ -278,7 +278,7 @@ namespace Rice
278
278
  // exceptions propogate back to Ruby
279
279
  return cpp_protect([&]
280
280
  {
281
- result->operator[](From_Ruby<T>().convert(key)) = From_Ruby<U>().convert(value);
281
+ result->insert_or_assign(From_Ruby<T>().convert(key), From_Ruby<U>().convert(value));
282
282
  return ST_CONTINUE;
283
283
  });
284
284
  }
@@ -102,6 +102,27 @@ namespace Rice::detail
102
102
  {
103
103
  };
104
104
 
105
+ // ref-qualified member Functions on C++ classes (C++20 uses these for std library types)
106
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
107
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) &> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
108
+ {
109
+ };
110
+
111
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
112
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) const&> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
113
+ {
114
+ };
115
+
116
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
117
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) & noexcept> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
118
+ {
119
+ };
120
+
121
+ template<typename Return_T, typename Class_T, typename...Parameter_Ts>
122
+ struct function_traits<Return_T(Class_T::*)(Parameter_Ts...) const& noexcept> : public function_traits<Return_T(Class_T*, Parameter_Ts...)>
123
+ {
124
+ };
125
+
105
126
  /*// Functors and lambdas
106
127
  template<class Function_T>
107
128
  struct function_traits<Function_T&> : public function_traits<Function_T>
@@ -590,4 +590,35 @@ TESTCASE(KeepAlive)
590
590
  // This should work because keepAlive prevents MyClass2 from being GC'd
591
591
  ASSERT_NOT_EQUAL(nullptr, dataStruct->myClass2);
592
592
  ASSERT_EQUAL(43, dataStruct->myClass2->value);
593
+ }
594
+
595
+ namespace
596
+ {
597
+ struct FuncPtrStruct
598
+ {
599
+ int (*callback)(int) = nullptr;
600
+ };
601
+ }
602
+
603
+ TESTCASE(function_pointer_attribute)
604
+ {
605
+ Module m = define_module("Testing");
606
+
607
+ Class c = define_class<FuncPtrStruct>("FuncPtrStruct")
608
+ .define_constructor(Constructor<FuncPtrStruct>())
609
+ .define_attr("callback", &FuncPtrStruct::callback);
610
+
611
+ Object o = c.call("new");
612
+
613
+ // Set the callback via Ruby using a lambda
614
+ std::string code = R"(struct = FuncPtrStruct.new
615
+ struct.callback = lambda { |x| x * 2 }
616
+ struct)";
617
+
618
+ Data_Object<FuncPtrStruct> funcPtrStruct = m.module_eval(code);
619
+
620
+ // Invoke the callback from C++ to verify it works
621
+ ASSERT_NOT_EQUAL(nullptr, funcPtrStruct->callback);
622
+ int result = funcPtrStruct->callback(5);
623
+ ASSERT_EQUAL(10, result);
593
624
  }
@@ -55,10 +55,8 @@ TESTCASE(LambdaCallBack)
55
55
  ASSERT((globalCallback != nullptr));
56
56
 
57
57
  int ref = 4;
58
- #pragma GCC diagnostic push
59
- #pragma GCC diagnostic ignored "-Wwrite-strings"
60
- char* result = triggerCallback(1, 2, true, "hello", ref);
61
- #pragma GCC diagnostic pop
58
+ char hello[] = "hello";
59
+ char* result = triggerCallback(1, 2, true, hello, ref);
62
60
  ASSERT_EQUAL("1 - 2.0 - true - hello - 4", result);
63
61
  }
64
62
 
@@ -615,3 +615,17 @@ TESTCASE(void_pointer_array)
615
615
  Object result = m.module_eval(code);
616
616
  ASSERT_EQUAL("[4, 3, 2, 1]", detail::From_Ruby<std::string>().convert(result.value()));
617
617
  }
618
+
619
+ TESTCASE(nullptr_t)
620
+ {
621
+ detail::From_Ruby<std::nullptr_t> fromRuby;
622
+
623
+ std::nullptr_t result = fromRuby.convert(Qnil);
624
+ ASSERT_EQUAL(nullptr, result);
625
+
626
+ ASSERT_EXCEPTION_CHECK(
627
+ Exception,
628
+ fromRuby.convert(rb_str_new2("not nil")),
629
+ ASSERT_EQUAL("wrong argument type String (expected nil)", ex.what())
630
+ );
631
+ }
@@ -365,6 +365,62 @@ TESTCASE(NotPrintable)
365
365
  ASSERT_EQUAL("[Not printable]", detail::From_Ruby<std::string>().convert(result));
366
366
  }
367
367
 
368
+ namespace
369
+ {
370
+ // Type with no default constructor - verifies insert_or_assign works in []=
371
+ class NoDefaultMap
372
+ {
373
+ public:
374
+ NoDefaultMap(int value) : value_(value) {}
375
+ int value() const { return value_; }
376
+
377
+ bool operator==(const NoDefaultMap& other) const
378
+ {
379
+ return this->value_ == other.value_;
380
+ }
381
+
382
+ private:
383
+ int value_;
384
+ };
385
+ }
386
+
387
+ TESTCASE(NoDefaultConstructor)
388
+ {
389
+ define_class<NoDefaultMap>("NoDefaultMap").
390
+ define_constructor(Constructor<NoDefaultMap, int>(), Arg("value")).
391
+ define_method("value", &NoDefaultMap::value);
392
+
393
+ Class c = define_map<std::string, NoDefaultMap>("NoDefaultMap_Map");
394
+
395
+ Object map = c.call("new");
396
+
397
+ // Test []= (insert_or_assign) with non-default-constructible value
398
+ map.call("[]=", "one", NoDefaultMap(1));
399
+ map.call("[]=", "two", NoDefaultMap(2));
400
+
401
+ Object result = map.call("size");
402
+ ASSERT_EQUAL(2, detail::From_Ruby<int32_t>().convert(result));
403
+
404
+ // Test [] access
405
+ result = map.call("[]", "one");
406
+ ASSERT_EQUAL(1, detail::From_Ruby<int>().convert(result.call("value")));
407
+
408
+ // Test update existing key
409
+ map.call("[]=", "one", NoDefaultMap(10));
410
+ result = map.call("[]", "one");
411
+ ASSERT_EQUAL(10, detail::From_Ruby<int>().convert(result.call("value")));
412
+
413
+ result = map.call("size");
414
+ ASSERT_EQUAL(2, detail::From_Ruby<int32_t>().convert(result));
415
+
416
+ // Test delete
417
+ result = map.call("delete", "two");
418
+ ASSERT_EQUAL(2, detail::From_Ruby<int>().convert(result.call("value")));
419
+
420
+ result = map.call("size");
421
+ ASSERT_EQUAL(1, detail::From_Ruby<int32_t>().convert(result));
422
+ }
423
+
368
424
  namespace
369
425
  {
370
426
  class Comparable
@@ -144,6 +144,38 @@ TESTCASE(AutoRegister)
144
144
  ASSERT(result.is_instance_of(pair.class_of()));
145
145
  }
146
146
 
147
+ namespace
148
+ {
149
+ // Type with no default constructor - like cv::detail::tracking::tbm::Track
150
+ class NoDefault
151
+ {
152
+ public:
153
+ NoDefault(int value) : value_(value) {}
154
+ int value() const { return value_; }
155
+ private:
156
+ int value_;
157
+ };
158
+ }
159
+
160
+ TESTCASE(PairNoDefaultConstructor)
161
+ {
162
+ define_class<NoDefault>("NoDefault").
163
+ define_constructor(Constructor<NoDefault, int>(), Arg("value")).
164
+ define_method("value", &NoDefault::value);
165
+
166
+ // This should compile and work even though NoDefault has no default constructor.
167
+ // The pair's default constructor should be skipped, but the two-argument constructor should work.
168
+ Class c = define_pair<const int, NoDefault>("IntNoDefaultPair");
169
+
170
+ Object pair = c.call("new", 42, NoDefault(7));
171
+
172
+ Object result = pair.call("first");
173
+ ASSERT_EQUAL(42, detail::From_Ruby<int>().convert(result));
174
+
175
+ result = pair.call("second");
176
+ ASSERT_EQUAL(7, detail::From_Ruby<int>().convert(result.call("value")));
177
+ }
178
+
147
179
  namespace
148
180
  {
149
181
  struct SomeStruct
@@ -609,6 +609,99 @@ TESTCASE(InheritedForwarding)
609
609
  ASSERT_EQUAL(456, detail::From_Ruby<int>().convert(result));
610
610
  }
611
611
 
612
+ // --- shared_ptr<const T> tests ---
613
+ // When shared_ptr wraps a const T, the Wrapper::get() method must handle
614
+ // the const-to-void* conversion. Without a cast, data_.get() returns
615
+ // const T* which cannot implicitly convert to void*.
616
+ namespace
617
+ {
618
+ class ConstTarget
619
+ {
620
+ public:
621
+ int value;
622
+
623
+ ConstTarget() : value(0) {}
624
+ ConstTarget(int v) : value(v) {}
625
+
626
+ int getValue() const { return value; }
627
+ };
628
+
629
+ std::shared_ptr<const ConstTarget> createConstTarget(int v)
630
+ {
631
+ return std::make_shared<const ConstTarget>(v);
632
+ }
633
+
634
+ int extractConstTargetValue(std::shared_ptr<const ConstTarget> ptr)
635
+ {
636
+ return ptr->getValue();
637
+ }
638
+ }
639
+
640
+ TESTCASE(SharedPtrConstT)
641
+ {
642
+ define_class<ConstTarget>("ConstTarget").
643
+ define_constructor(Constructor<ConstTarget, int>(),
644
+ Arg("v")).
645
+ define_method("get_value", &ConstTarget::getValue);
646
+
647
+ Module m = define_module("SharedPtrConstTest").
648
+ define_module_function("create_const_target", &createConstTarget).
649
+ define_module_function("extract_const_target_value", &extractConstTargetValue);
650
+
651
+ // Test that shared_ptr<const T> can be unwrapped to access the inner T
652
+ std::string code = R"(ptr = create_const_target(42)
653
+ extract_const_target_value(ptr))";
654
+
655
+ Object result = m.instance_eval(code);
656
+ ASSERT_EQUAL(42, detail::From_Ruby<int>().convert(result.value()));
657
+ }
658
+
659
+ // Abstract class with non-virtual destructor - like cv::cudacodec::NVSurfaceToColorConverter
660
+ namespace
661
+ {
662
+ class AbstractClass
663
+ {
664
+ public:
665
+ virtual bool compute(int value) const = 0;
666
+ ~AbstractClass() {} // non-virtual destructor
667
+ };
668
+
669
+ class ConcreteImpl : public AbstractClass
670
+ {
671
+ public:
672
+ bool compute(int value) const override { return value > 0; }
673
+ };
674
+
675
+ std::shared_ptr<AbstractClass> createAbstract()
676
+ {
677
+ return std::make_shared<ConcreteImpl>();
678
+ }
679
+
680
+ bool useAbstract(std::shared_ptr<AbstractClass> ptr, int value)
681
+ {
682
+ return ptr->compute(value);
683
+ }
684
+ }
685
+
686
+ TESTCASE(SharedPtrAbstractT)
687
+ {
688
+ // shared_ptr<T> where T is abstract should NOT define a constructor taking T*,
689
+ // because deleting through a non-virtual destructor on an abstract class is UB.
690
+ define_class<AbstractClass>("AbstractClass").
691
+ define_method("compute", &AbstractClass::compute,
692
+ Arg("value"));
693
+
694
+ Module m = define_module("SharedPtrAbstractTest").
695
+ define_module_function("create_abstract", &createAbstract).
696
+ define_module_function("use_abstract", &useAbstract);
697
+
698
+ std::string code = R"(ptr = create_abstract
699
+ use_abstract(ptr, 5))";
700
+
701
+ Object result = m.instance_eval(code);
702
+ ASSERT_EQUAL(Qtrue, result.value());
703
+ }
704
+
612
705
  // Forward declaration only - IncompleteClass is never defined
613
706
  class IncompleteClass;
614
707
 
@@ -252,4 +252,51 @@ TESTCASE(Release)
252
252
  Exception,
253
253
  m.module_eval(code),
254
254
  ASSERT(std::string(ex.what()).find("undefined method") == 0));
255
+ }
256
+
257
+ // --- unique_ptr<const T> tests ---
258
+ // When unique_ptr wraps a const T, the Wrapper::get() method must handle
259
+ // the const-to-void* conversion. Without a cast, data_.get() returns
260
+ // const T* which cannot implicitly convert to void*.
261
+ namespace
262
+ {
263
+ class ConstTarget
264
+ {
265
+ public:
266
+ int value;
267
+
268
+ ConstTarget() : value(0) {}
269
+ ConstTarget(int v) : value(v) {}
270
+
271
+ int getValue() const { return value; }
272
+ };
273
+
274
+ std::unique_ptr<const ConstTarget> createConstTarget(int v)
275
+ {
276
+ return std::make_unique<const ConstTarget>(v);
277
+ }
278
+
279
+ int extractConstTargetValue(const std::unique_ptr<const ConstTarget>& ptr)
280
+ {
281
+ return ptr->getValue();
282
+ }
283
+ }
284
+
285
+ TESTCASE(UniquePtrConstT)
286
+ {
287
+ define_class<ConstTarget>("ConstTarget").
288
+ define_constructor(Constructor<ConstTarget, int>(),
289
+ Arg("v")).
290
+ define_method("get_value", &ConstTarget::getValue);
291
+
292
+ Module m = define_module("UniquePtrConstTest").
293
+ define_module_function("create_const_target", &createConstTarget).
294
+ define_module_function("extract_const_target_value", &extractConstTargetValue);
295
+
296
+ // Test that unique_ptr<const T> can be unwrapped to access the inner T
297
+ std::string code = R"(ptr = create_const_target(42)
298
+ extract_const_target_value(ptr))";
299
+
300
+ Object result = m.instance_eval(code);
301
+ ASSERT_EQUAL(42, detail::From_Ruby<int>().convert(result.value()));
255
302
  }
@@ -357,6 +357,62 @@ TESTCASE(NotPrintable)
357
357
  ASSERT_EQUAL("[Not printable]", detail::From_Ruby<std::string>().convert(result));
358
358
  }
359
359
 
360
+ namespace
361
+ {
362
+ // Type with no default constructor - verifies insert_or_assign works in []=
363
+ class NoDefaultUnorderedMap
364
+ {
365
+ public:
366
+ NoDefaultUnorderedMap(int value) : value_(value) {}
367
+ int value() const { return value_; }
368
+
369
+ bool operator==(const NoDefaultUnorderedMap& other) const
370
+ {
371
+ return this->value_ == other.value_;
372
+ }
373
+
374
+ private:
375
+ int value_;
376
+ };
377
+ }
378
+
379
+ TESTCASE(NoDefaultConstructor)
380
+ {
381
+ define_class<NoDefaultUnorderedMap>("NoDefaultUnorderedMap").
382
+ define_constructor(Constructor<NoDefaultUnorderedMap, int>(), Arg("value")).
383
+ define_method("value", &NoDefaultUnorderedMap::value);
384
+
385
+ Class c = define_unordered_map<std::string, NoDefaultUnorderedMap>("NoDefaultUnorderedMap_Map");
386
+
387
+ Object map = c.call("new");
388
+
389
+ // Test []= (insert_or_assign) with non-default-constructible value
390
+ map.call("[]=", "one", NoDefaultUnorderedMap(1));
391
+ map.call("[]=", "two", NoDefaultUnorderedMap(2));
392
+
393
+ Object result = map.call("size");
394
+ ASSERT_EQUAL(2, detail::From_Ruby<int32_t>().convert(result));
395
+
396
+ // Test [] access
397
+ result = map.call("[]", "one");
398
+ ASSERT_EQUAL(1, detail::From_Ruby<int>().convert(result.call("value")));
399
+
400
+ // Test update existing key
401
+ map.call("[]=", "one", NoDefaultUnorderedMap(10));
402
+ result = map.call("[]", "one");
403
+ ASSERT_EQUAL(10, detail::From_Ruby<int>().convert(result.call("value")));
404
+
405
+ result = map.call("size");
406
+ ASSERT_EQUAL(2, detail::From_Ruby<int32_t>().convert(result));
407
+
408
+ // Test delete
409
+ result = map.call("delete", "two");
410
+ ASSERT_EQUAL(2, detail::From_Ruby<int>().convert(result.call("value")));
411
+
412
+ result = map.call("size");
413
+ ASSERT_EQUAL(1, detail::From_Ruby<int32_t>().convert(result));
414
+ }
415
+
360
416
  namespace
361
417
  {
362
418
  class Comparable
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rice
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.11.2
4
+ version: 4.11.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Brannan