rice 4.11.1 → 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: 935f187922d58bf0dc585e30ae66b5454bb8f87ac8c640ca38d9445371991dff
4
- data.tar.gz: d00d336620c52f7e3a56941d6402109bcc20d3a73dc4090fd3d49395cbbdc7e7
3
+ metadata.gz: 2b52ff5ddc64209e1c252d529111695d833a57d1362e15b6d223777194dc6750
4
+ data.tar.gz: 214c2b675683e68d026440cc83499a7de0faa6282563c0bfa028c925e677f195
5
5
  SHA512:
6
- metadata.gz: 9c006c97da6e586d836a08b51c2d1ed37e8921e11f0dc30f45d3fcfeb5166ae5172610b958e0b9fe35a8eddb199af05b88eec1bf272541b7a39d890b0856bacf
7
- data.tar.gz: 7795809caf1d34330279d5fb454cb935b08422b531522d721d4ba3185442426d51fd1f8795dfbbbb34daff9a19d586e0e5554925d69eed7d57aebcbba9017a6a
6
+ metadata.gz: fdee833532115137ec7e375e83e377c5e21a634b44e0b67bb5954ea3ad04a2983ef3c6ffc5d5deb804b1d94c7b76f04d2cc94b73b054aaa0722f01fd39f58c37
7
+ data.tar.gz: 23e9990bbf398a067608c29028018840506254f3a8a00a7f6cc56840ec88e70007d0309656b297b20ab31776805b3deccb14e16fface820b0ec7d3340aafc0f3
data/CHANGELOG.md CHANGED
@@ -1,12 +1,38 @@
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
+
23
+ ## 4.11.2 (2026-02-21)
24
+
25
+ ### Enhancements
26
+
27
+ * Add support for `long double`
28
+ * Improve support for references to incomplete types
29
+
3
30
  ## 4.11.1 (2026-02-18)
4
31
 
5
32
  ### Enhancements
6
33
 
7
34
  * Be more lenient on wrapp Qnil values in the C++ API
8
35
 
9
-
10
36
  ## 4.11.0 (2026-02-17)
11
37
 
12
38
  This release focuses on improving memory management.
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>
@@ -3797,6 +3818,17 @@ namespace Rice::detail
3797
3818
  static inline std::string name = "Float";
3798
3819
  };
3799
3820
 
3821
+ template<>
3822
+ class RubyType<long double>
3823
+ {
3824
+ public:
3825
+ using FromRuby_T = double(*)(VALUE);
3826
+
3827
+ static inline FromRuby_T fromRuby = rb_num2dbl;
3828
+ static inline std::string packTemplate = "d*";
3829
+ static inline std::string name = "Float";
3830
+ };
3831
+
3800
3832
  template<>
3801
3833
  class RubyType<void>
3802
3834
  {
@@ -6073,6 +6105,35 @@ namespace Rice::detail
6073
6105
  }
6074
6106
  };
6075
6107
 
6108
+ template<>
6109
+ struct Type<long double>
6110
+ {
6111
+ static bool verify()
6112
+ {
6113
+ return true;
6114
+ }
6115
+
6116
+ static VALUE rubyKlass()
6117
+ {
6118
+ return rb_cFloat;
6119
+ }
6120
+ };
6121
+
6122
+ template<int N>
6123
+ struct Type<long double[N]>
6124
+ {
6125
+ static bool verify()
6126
+ {
6127
+ define_buffer<long double>();
6128
+ return true;
6129
+ }
6130
+
6131
+ static VALUE rubyKlass()
6132
+ {
6133
+ return rb_cString;
6134
+ }
6135
+ };
6136
+
6076
6137
  template<>
6077
6138
  struct Type<void>
6078
6139
  {
@@ -6601,6 +6662,62 @@ namespace Rice
6601
6662
  Arg* arg_ = nullptr;
6602
6663
  };
6603
6664
 
6665
+ // =========== long double ============
6666
+ template<>
6667
+ class To_Ruby<long double>
6668
+ {
6669
+ public:
6670
+ To_Ruby() = default;
6671
+
6672
+ explicit To_Ruby(Arg* arg) : arg_(arg)
6673
+ {}
6674
+
6675
+ VALUE convert(const long double& native)
6676
+ {
6677
+ return protect(rb_float_new, native);
6678
+ }
6679
+
6680
+ private:
6681
+ Arg* arg_ = nullptr;
6682
+ };
6683
+
6684
+ template<>
6685
+ class To_Ruby<long double&>
6686
+ {
6687
+ public:
6688
+ To_Ruby() = default;
6689
+
6690
+ explicit To_Ruby(Arg* arg) : arg_(arg)
6691
+ {}
6692
+
6693
+ VALUE convert(const long double& native)
6694
+ {
6695
+ return protect(rb_float_new, native);
6696
+ }
6697
+
6698
+ private:
6699
+ Arg* arg_ = nullptr;
6700
+ };
6701
+
6702
+ template<int N>
6703
+ class To_Ruby<long double[N]>
6704
+ {
6705
+ public:
6706
+ To_Ruby() = default;
6707
+
6708
+ explicit To_Ruby(Arg* arg) : arg_(arg)
6709
+ {}
6710
+
6711
+ VALUE convert(long double data[N])
6712
+ {
6713
+ Buffer<long double> buffer(data, N);
6714
+ Data_Object<Buffer<long double>> dataObject(std::move(buffer));
6715
+ return dataObject.value();
6716
+ }
6717
+ private:
6718
+ Arg* arg_ = nullptr;
6719
+ };
6720
+
6604
6721
  // =========== float ============
6605
6722
  template<>
6606
6723
  class To_Ruby<float>
@@ -7879,6 +7996,86 @@ namespace Rice::detail
7879
7996
  Reference<double> reference_;
7880
7997
  };
7881
7998
 
7999
+ // =========== long double ============
8000
+ template<>
8001
+ class From_Ruby<long double>
8002
+ {
8003
+ public:
8004
+ From_Ruby() = default;
8005
+
8006
+ explicit From_Ruby(Arg* arg) : arg_(arg)
8007
+ {}
8008
+
8009
+ long double is_convertible(VALUE value)
8010
+ {
8011
+ return FromRubyFundamental<long double>::is_convertible(value);
8012
+ }
8013
+
8014
+ long double convert(VALUE value)
8015
+ {
8016
+ return FromRubyFundamental<long double>::convert(value);
8017
+ }
8018
+
8019
+ private:
8020
+ Arg* arg_ = nullptr;
8021
+ };
8022
+
8023
+ template<>
8024
+ class From_Ruby<long double&>
8025
+ {
8026
+ public:
8027
+ using Reference_T = Reference<long double>;
8028
+
8029
+ From_Ruby() = default;
8030
+
8031
+ explicit From_Ruby(Arg* arg) : arg_(arg)
8032
+ {}
8033
+
8034
+ long double is_convertible(VALUE value)
8035
+ {
8036
+ switch (rb_type(value))
8037
+ {
8038
+ case RUBY_T_DATA:
8039
+ {
8040
+ if (Data_Type<Reference_T>::is_descendant(value))
8041
+ {
8042
+ return Convertible::Exact;
8043
+ }
8044
+ [[fallthrough]];
8045
+ }
8046
+ default:
8047
+ {
8048
+ return FromRubyFundamental<long double>::is_convertible(value);
8049
+ }
8050
+ }
8051
+ }
8052
+
8053
+ long double& convert(VALUE value)
8054
+ {
8055
+ switch (rb_type(value))
8056
+ {
8057
+ case RUBY_T_DATA:
8058
+ {
8059
+ if (Data_Type<Reference_T>::is_descendant(value))
8060
+ {
8061
+ Reference_T* reference = unwrap<Reference_T>(value, Data_Type<Reference_T>::ruby_data_type(), false);
8062
+ return reference->get();
8063
+ }
8064
+ [[fallthrough]];
8065
+ }
8066
+ default:
8067
+ {
8068
+ this->reference_ = Reference<long double>(value);
8069
+ return this->reference_.get();
8070
+ }
8071
+ }
8072
+ }
8073
+
8074
+ private:
8075
+ Arg* arg_ = nullptr;
8076
+ Reference<long double> reference_;
8077
+ };
8078
+
7882
8079
  // =========== float ============
7883
8080
  template<>
7884
8081
  class From_Ruby<float>
@@ -8678,31 +8875,15 @@ namespace Rice::detail
8678
8875
  }
8679
8876
  }
8680
8877
 
8681
- void* convert(VALUE value)
8878
+ std::nullptr_t convert(VALUE value)
8682
8879
  {
8683
8880
  if (value == Qnil)
8684
8881
  {
8685
8882
  return nullptr;
8686
8883
  }
8687
8884
 
8688
- if (this->arg_ && this->arg_->isOpaque())
8689
- {
8690
- return (void*)value;
8691
- }
8692
-
8693
- switch (rb_type(value))
8694
- {
8695
- case RUBY_T_NIL:
8696
- {
8697
- return nullptr;
8698
- break;
8699
- }
8700
- default:
8701
- {
8702
- throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
8703
- detail::protect(rb_obj_classname, value), "nil");
8704
- }
8705
- }
8885
+ throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
8886
+ detail::protect(rb_obj_classname, value), "nil");
8706
8887
  }
8707
8888
  private:
8708
8889
  Arg* arg_ = nullptr;
@@ -8973,6 +9154,12 @@ namespace Rice::detail
8973
9154
  {
8974
9155
  return std::type_index(typeid(T));
8975
9156
  }
9157
+ else if constexpr (std::is_reference_v<T>)
9158
+ {
9159
+ // For incomplete reference types, strip the reference and use pointer.
9160
+ // Can't form T* when T is a reference type (pointer-to-reference is illegal).
9161
+ return std::type_index(typeid(std::remove_reference_t<T>*));
9162
+ }
8976
9163
  else
8977
9164
  {
8978
9165
  return std::type_index(typeid(T*));
@@ -10095,7 +10282,9 @@ namespace Rice::detail
10095
10282
 
10096
10283
  if constexpr (is_complete_v<T>)
10097
10284
  {
10098
- 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>)
10099
10288
  {
10100
10289
  if (this->isOwner_)
10101
10290
  {
@@ -14845,8 +15034,6 @@ namespace Rice
14845
15034
  template <typename Attribute_T, typename Access_T, typename...Arg_Ts>
14846
15035
  inline Data_Type<T>& Data_Type<T>::define_attr_internal(VALUE klass, std::string name, Attribute_T attribute, Access_T, const Arg_Ts&...args)
14847
15036
  {
14848
- using Attr_T = typename detail::attribute_traits<Attribute_T>::attr_type;
14849
-
14850
15037
  // Define attribute getter
14851
15038
  if constexpr (std::is_same_v<Access_T, AttrAccess::ReadWriteType> || std::is_same_v<Access_T, AttrAccess::ReadType>)
14852
15039
  {
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.1"
2
+ VERSION = "4.11.3"
3
3
  end
data/rice/Data_Type.ipp CHANGED
@@ -376,8 +376,6 @@ namespace Rice
376
376
  template <typename Attribute_T, typename Access_T, typename...Arg_Ts>
377
377
  inline Data_Type<T>& Data_Type<T>::define_attr_internal(VALUE klass, std::string name, Attribute_T attribute, Access_T, const Arg_Ts&...args)
378
378
  {
379
- using Attr_T = typename detail::attribute_traits<Attribute_T>::attr_type;
380
-
381
379
  // Define attribute getter
382
380
  if constexpr (std::is_same_v<Access_T, AttrAccess::ReadWriteType> || std::is_same_v<Access_T, AttrAccess::ReadType>)
383
381
  {
@@ -154,6 +154,17 @@ namespace Rice::detail
154
154
  static inline std::string name = "Float";
155
155
  };
156
156
 
157
+ template<>
158
+ class RubyType<long double>
159
+ {
160
+ public:
161
+ using FromRuby_T = double(*)(VALUE);
162
+
163
+ static inline FromRuby_T fromRuby = rb_num2dbl;
164
+ static inline std::string packTemplate = "d*";
165
+ static inline std::string name = "Float";
166
+ };
167
+
157
168
  template<>
158
169
  class RubyType<void>
159
170
  {
@@ -13,6 +13,12 @@ namespace Rice::detail
13
13
  {
14
14
  return std::type_index(typeid(T));
15
15
  }
16
+ else if constexpr (std::is_reference_v<T>)
17
+ {
18
+ // For incomplete reference types, strip the reference and use pointer.
19
+ // Can't form T* when T is a reference type (pointer-to-reference is illegal).
20
+ return std::type_index(typeid(std::remove_reference_t<T>*));
21
+ }
16
22
  else
17
23
  {
18
24
  return std::type_index(typeid(T*));
@@ -404,6 +404,35 @@ namespace Rice::detail
404
404
  }
405
405
  };
406
406
 
407
+ template<>
408
+ struct Type<long double>
409
+ {
410
+ static bool verify()
411
+ {
412
+ return true;
413
+ }
414
+
415
+ static VALUE rubyKlass()
416
+ {
417
+ return rb_cFloat;
418
+ }
419
+ };
420
+
421
+ template<int N>
422
+ struct Type<long double[N]>
423
+ {
424
+ static bool verify()
425
+ {
426
+ define_buffer<long double>();
427
+ return true;
428
+ }
429
+
430
+ static VALUE rubyKlass()
431
+ {
432
+ return rb_cString;
433
+ }
434
+ };
435
+
407
436
  template<>
408
437
  struct Type<void>
409
438
  {
@@ -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
  {
@@ -729,6 +729,86 @@ namespace Rice::detail
729
729
  Reference<double> reference_;
730
730
  };
731
731
 
732
+ // =========== long double ============
733
+ template<>
734
+ class From_Ruby<long double>
735
+ {
736
+ public:
737
+ From_Ruby() = default;
738
+
739
+ explicit From_Ruby(Arg* arg) : arg_(arg)
740
+ {}
741
+
742
+ long double is_convertible(VALUE value)
743
+ {
744
+ return FromRubyFundamental<long double>::is_convertible(value);
745
+ }
746
+
747
+ long double convert(VALUE value)
748
+ {
749
+ return FromRubyFundamental<long double>::convert(value);
750
+ }
751
+
752
+ private:
753
+ Arg* arg_ = nullptr;
754
+ };
755
+
756
+ template<>
757
+ class From_Ruby<long double&>
758
+ {
759
+ public:
760
+ using Reference_T = Reference<long double>;
761
+
762
+ From_Ruby() = default;
763
+
764
+ explicit From_Ruby(Arg* arg) : arg_(arg)
765
+ {}
766
+
767
+ long double is_convertible(VALUE value)
768
+ {
769
+ switch (rb_type(value))
770
+ {
771
+ case RUBY_T_DATA:
772
+ {
773
+ if (Data_Type<Reference_T>::is_descendant(value))
774
+ {
775
+ return Convertible::Exact;
776
+ }
777
+ [[fallthrough]];
778
+ }
779
+ default:
780
+ {
781
+ return FromRubyFundamental<long double>::is_convertible(value);
782
+ }
783
+ }
784
+ }
785
+
786
+ long double& convert(VALUE value)
787
+ {
788
+ switch (rb_type(value))
789
+ {
790
+ case RUBY_T_DATA:
791
+ {
792
+ if (Data_Type<Reference_T>::is_descendant(value))
793
+ {
794
+ Reference_T* reference = unwrap<Reference_T>(value, Data_Type<Reference_T>::ruby_data_type(), false);
795
+ return reference->get();
796
+ }
797
+ [[fallthrough]];
798
+ }
799
+ default:
800
+ {
801
+ this->reference_ = Reference<long double>(value);
802
+ return this->reference_.get();
803
+ }
804
+ }
805
+ }
806
+
807
+ private:
808
+ Arg* arg_ = nullptr;
809
+ Reference<long double> reference_;
810
+ };
811
+
732
812
  // =========== float ============
733
813
  template<>
734
814
  class From_Ruby<float>
@@ -1528,31 +1608,15 @@ namespace Rice::detail
1528
1608
  }
1529
1609
  }
1530
1610
 
1531
- void* convert(VALUE value)
1611
+ std::nullptr_t convert(VALUE value)
1532
1612
  {
1533
1613
  if (value == Qnil)
1534
1614
  {
1535
1615
  return nullptr;
1536
1616
  }
1537
1617
 
1538
- if (this->arg_ && this->arg_->isOpaque())
1539
- {
1540
- return (void*)value;
1541
- }
1542
-
1543
- switch (rb_type(value))
1544
- {
1545
- case RUBY_T_NIL:
1546
- {
1547
- return nullptr;
1548
- break;
1549
- }
1550
- default:
1551
- {
1552
- throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
1553
- detail::protect(rb_obj_classname, value), "nil");
1554
- }
1555
- }
1618
+ throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
1619
+ detail::protect(rb_obj_classname, value), "nil");
1556
1620
  }
1557
1621
  private:
1558
1622
  Arg* arg_ = nullptr;
@@ -501,6 +501,62 @@ namespace Rice
501
501
  Arg* arg_ = nullptr;
502
502
  };
503
503
 
504
+ // =========== long double ============
505
+ template<>
506
+ class To_Ruby<long double>
507
+ {
508
+ public:
509
+ To_Ruby() = default;
510
+
511
+ explicit To_Ruby(Arg* arg) : arg_(arg)
512
+ {}
513
+
514
+ VALUE convert(const long double& native)
515
+ {
516
+ return protect(rb_float_new, native);
517
+ }
518
+
519
+ private:
520
+ Arg* arg_ = nullptr;
521
+ };
522
+
523
+ template<>
524
+ class To_Ruby<long double&>
525
+ {
526
+ public:
527
+ To_Ruby() = default;
528
+
529
+ explicit To_Ruby(Arg* arg) : arg_(arg)
530
+ {}
531
+
532
+ VALUE convert(const long double& native)
533
+ {
534
+ return protect(rb_float_new, native);
535
+ }
536
+
537
+ private:
538
+ Arg* arg_ = nullptr;
539
+ };
540
+
541
+ template<int N>
542
+ class To_Ruby<long double[N]>
543
+ {
544
+ public:
545
+ To_Ruby() = default;
546
+
547
+ explicit To_Ruby(Arg* arg) : arg_(arg)
548
+ {}
549
+
550
+ VALUE convert(long double data[N])
551
+ {
552
+ Buffer<long double> buffer(data, N);
553
+ Data_Object<Buffer<long double>> dataObject(std::move(buffer));
554
+ return dataObject.value();
555
+ }
556
+ private:
557
+ Arg* arg_ = nullptr;
558
+ };
559
+
504
560
  // =========== float ============
505
561
  template<>
506
562
  class To_Ruby<float>
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.1
4
+ version: 4.11.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Brannan