kblib 0.2.3
General utilities library for modern C++
poly_obj.cpp
Go to the documentation of this file.
1#include "kblib/poly_obj.h"
2
3#include "catch.hpp"
4
5#if KBLIB_USE_CXX17
6
7namespace {
8
9enum barks {
10 vgood_base,
11 dgood_base,
12
13 vgood_derived,
14 dgood_derived,
15
16 vbad_base1,
17 dbad_base1,
18
19 vbad_derived1,
20 dbad_derived1,
21
22 vbad_base2,
23 dbad_base2,
24
25 vbad_derived2,
26 dbad_derived2,
27
28 vsmall_base,
29 dsmall_base,
30
31 vbig_derived,
32 dbig_derived,
33};
34
35std::vector<barks> bark_log;
36
37struct good_base {
38 good_base() = default;
39 virtual ~good_base() noexcept { bark_log.push_back(dgood_base); }
40
41 virtual auto bark() const -> void { bark_log.push_back(vgood_base); }
42};
43struct good_derived : good_base {
44 ~good_derived() noexcept override { bark_log.push_back(dgood_derived); }
45
46 auto bark() const -> void override { bark_log.push_back(vgood_derived); }
47};
48struct unrelated {};
49struct bad_nocopy : good_base {
50 bad_nocopy(const bad_nocopy&) = delete;
51};
52
53struct bad_base1 {
54 virtual auto bark() const -> void { bark_log.push_back(vbad_base1); }
55 // non-virtual destructor
56 ~bad_base1() noexcept { bark_log.push_back(dbad_base1); }
57};
58struct bad_derived1 : bad_base1 {
59 auto bark() const -> void override { bark_log.push_back(vbad_derived1); }
60 KBLIB_UNUSED ~bad_derived1() noexcept { bark_log.push_back(dbad_derived1); }
61};
62
63struct bad_base2 {
64 bad_base2() = default;
65 virtual ~bad_base2() noexcept { bark_log.push_back(dbad_base2); }
66 virtual auto bark() const -> void { bark_log.push_back(vbad_base2); }
67};
68struct bad_derived2 : protected bad_base2 {
69 auto bark() const -> void override { bark_log.push_back(vbad_derived2); }
70 ~bad_derived2() noexcept override { bark_log.push_back(dbad_derived2); }
71};
72struct small_base {
73 small_base() = default;
74 virtual ~small_base() noexcept = default;
75 virtual auto bark() const -> void { bark_log.push_back(vsmall_base); }
76 virtual auto id() const -> int { return 0; }
77};
78struct big_derived : small_base {
79 std::size_t x
80 = (static_cast<void>(x = 1),
81 kblib::FNVa_a<std::size_t>(
82 reinterpret_cast<const char (&)[sizeof(big_derived)]>(*this)));
83 auto bark() const -> void override { bark_log.push_back(vbig_derived); }
84 ~big_derived() noexcept override = default;
85 auto id() const -> int override { return 1; }
86};
87
88struct not_copyable {
89 not_copyable() = default;
90 not_copyable(const not_copyable&) = delete;
91 KBLIB_UNUSED not_copyable(not_copyable&&) noexcept = default;
92 virtual ~not_copyable() = default;
93};
94
95struct copyable_derived : not_copyable {
96 KBLIB_UNUSED copyable_derived() = default;
97 copyable_derived(const copyable_derived&) {}
98 copyable_derived(copyable_derived&&) noexcept = default;
99};
100
101struct copyable_base {
102 virtual ~copyable_base() = default;
103};
104
105struct noncopyable_derived : copyable_base {
106 KBLIB_UNUSED noncopyable_derived() = default;
107 noncopyable_derived(const noncopyable_derived&) = delete;
108 KBLIB_UNUSED noncopyable_derived(noncopyable_derived&&) = default;
109};
110
111struct hinted_base {
112 constexpr static std::size_t max_derived_size = sizeof(big_derived);
113 virtual ~hinted_base() = default;
114};
115
116struct hinted_derived : hinted_base {
117 int member;
118};
119
120template <typename T>
121struct print;
122
123} // namespace
124
125TEST_CASE("poly_obj") {
126 SECTION("basic") {
127 std::vector<barks> expected;
128 bark_log.clear();
129 {
130 kblib::poly_obj<good_base> o1, o2{std::in_place};
131 REQUIRE(not o1.has_value());
132 REQUIRE(o2.has_value());
133 o2->bark(); // good_base
134 expected.push_back(vgood_base);
135 REQUIRE(bark_log == expected);
136
138 // destroyed
139 expected.push_back(dgood_derived);
140 expected.push_back(dgood_base);
141 REQUIRE(bark_log == expected);
142 REQUIRE(o1.has_value());
143
144 o1->bark(); // good_derived
145 expected.push_back(vgood_derived);
146 REQUIRE(bark_log == expected);
147
148 o2 = o1; // o2 ~good_base
149 expected.push_back(dgood_base);
150 REQUIRE(bark_log == expected);
151
152 o2->bark(); // good_derived
153 expected.push_back(vgood_derived);
154 REQUIRE(bark_log == expected);
155
156 o1.clear(); //~good_derived, ~good_base
157 expected.push_back(dgood_derived);
158 expected.push_back(dgood_base);
159 REQUIRE(bark_log == expected);
160 REQUIRE(not o1.has_value());
161 REQUIRE(o2.has_value());
162 // kblib::poly_obj<good_base> o3 =
163 // kblib::poly_obj<good_base>::make<bad_nocopy>();
164 } // o2: ~good_derived, ~good_base
165
166 expected.push_back(dgood_derived);
167 expected.push_back(dgood_base);
168 REQUIRE(bark_log == expected);
169 }
170
171 SECTION("non-virtual destructor") {
172 // static_assertion fails because of non-virtual destructor
173 // kblib::poly_obj<bad_base1> o3, o4{std::in_place};
174 // o3 = kblib::poly_obj<bad_base1>::make<bad_derived1>();
175 }
176
177 SECTION("private base") {
178 std::vector<barks> expected;
179 bark_log.clear();
180 {
181 kblib::poly_obj<bad_base2> o5, o6{std::in_place};
182 // illegal - non-public base
183 // o5 = kblib::poly_obj<bad_base2>::make<bad_derived2>();
184 }
185 expected.push_back(dbad_base2);
186 REQUIRE(bark_log == expected);
187 }
188
189 SECTION("capacity") {
190 std::vector<barks> expected;
191 bark_log.clear();
192
193 // illegal - unrelated types
194 // kblib::poly_obj<good_base>::make<unrelated>();
195 // illegal - derived too big
196 // kblib::poly_obj<small_base>::make<big_derived>();
197 // fine
198 kblib::poly_obj<small_base, sizeof(big_derived)> o7, o8{std::in_place};
199 o8->bark();
200 expected.push_back(vsmall_base);
201 REQUIRE(bark_log == expected);
202 o7 = kblib::poly_obj<small_base,
203 sizeof(big_derived)>::make<big_derived>();
204 REQUIRE(o7.has_value());
205 o7->bark();
206 expected.push_back(vbig_derived);
207 REQUIRE(bark_log == expected);
208 }
209
210 SECTION("non-copyable type") {
211 // A poly_obj of non-copyable type is allowed and non-copyable
212 kblib::poly_obj<not_copyable> o1{std::in_place}, o2;
213 // illegal
214 // o2 = o1;
215 // legal
216 o2 = std::move(o1);
217 // legal
219 // illegal (can't copy even if derived is copyable)
220 // o1 = o3;
221 }
222
223 SECTION("non-copyable derived") {
224 kblib::poly_obj<copyable_base, sizeof(copyable_base),
226 o1{std::in_place};
227 // Valid because the template parameters explicitly disable copying, so
228 // derived classes don't need to be copyable.
229 auto o2 = decltype(o1)::make<noncopyable_derived>();
230 }
231
232 SECTION("mixed vector") {
233 auto r = [n = std::uint16_t{16127}]() mutable {
234 n ^= 19937u;
235 n *= 4889;
236 return kblib::FNV32a({reinterpret_cast<char*>(&n), sizeof(n)});
237 };
238
239 using obj = kblib::poly_obj<small_base, sizeof(big_derived)>;
240
241 std::vector<obj> polyvec;
242 int g[2] = {};
243 std::generate_n(std::back_inserter(polyvec), 128, [&] {
244 if ((r() % 2)) {
245 ++g[1];
246 return obj::make<big_derived>();
247 } else {
248 ++g[0];
249 return obj(std::in_place);
250 }
251 });
252 REQUIRE(g[0] == 64);
253 REQUIRE(g[1] == 64);
254
255 int c[2] = {};
256 // char ab[] = "ab";
257 for (const auto& o : polyvec) {
258 ++c[o->id()];
259 // std::cout << ab[o->id()];
260 }
261 REQUIRE(c[0] == 64);
262 REQUIRE(c[1] == 64);
263 }
264
265 SECTION("free make function") {
266 // Automatically takes the larger capacity value, but returns a base
267 // pointer
268 auto o = kblib::make_poly_obj<small_base, big_derived>();
269 static_assert(
270 std::is_same_v<decltype(o),
271 kblib::poly_obj<small_base, sizeof(big_derived)>>,
272 "make_poly_obj must return the correct type.");
273 }
274 SECTION("hinted") {
276 == hinted_base::max_derived_size);
278 static_assert(decltype(o)::capacity
279 == decltype(o)::base_type::max_derived_size);
280 o = decltype(o)::make<hinted_derived>();
281 }
282}
283
284namespace {
285struct mem_ptr_test {
286 int member{42};
287 auto get_member() & noexcept -> int { return member; }
288 auto cget_member() const& noexcept -> int { return member; }
289 auto rget_member() && noexcept -> int { return member; }
290 auto crget_member() const&& noexcept -> int { return member; }
291
292 auto get_member2() noexcept -> int { return member; }
293 auto cget_member2() const noexcept -> int { return member; }
294
295 virtual ~mem_ptr_test() = default;
296};
297} // namespace
298
299TEST_CASE("poly_obj->*") {
300 kblib::poly_obj<mem_ptr_test> obj{std::in_place};
301 SECTION("member data") {
302 constexpr auto data_member = &mem_ptr_test::member;
303 REQUIRE(obj->*data_member == 42);
304 static_assert(std::is_same_v<decltype(obj->*data_member), int&>);
305 static_assert(std::is_same_v<decltype(std::as_const(obj)->*data_member),
306 const int&>);
307 static_assert(
308 std::is_same_v<decltype(std::move(obj)->*data_member), int&&>);
309 static_assert(
310 std::is_same_v<decltype(std::move(std::as_const(obj))->*data_member),
311 const int&&>);
312 }
313 SECTION("member function") {
314 constexpr auto func_member = &mem_ptr_test::get_member;
315 constexpr auto cfunc_member = &mem_ptr_test::cget_member;
316 constexpr auto rfunc_member = &mem_ptr_test::rget_member;
317 constexpr auto crfunc_member = &mem_ptr_test::crget_member;
318 REQUIRE((obj->*func_member)() == 42);
319 static_assert(std::is_same_v<decltype((obj->*func_member)()), int>);
320 REQUIRE((obj->*cfunc_member)() == 42);
321 static_assert(std::is_same_v<decltype((obj->*cfunc_member)()), int>);
322 REQUIRE((std::as_const(obj)->*cfunc_member)() == 42);
323 static_assert(
324 std::is_same_v<decltype((std::as_const(obj)->*cfunc_member)()), int>);
325 REQUIRE((std::move(obj)->*rfunc_member)() == 42);
326 static_assert(
327 std::is_same_v<decltype((std::move(obj)->*rfunc_member)()), int>);
328 REQUIRE((std::move(obj)->*crfunc_member)() == 42);
329 static_assert(
330 std::is_same_v<decltype((std::move(obj)->*crfunc_member)()), int>);
331 REQUIRE((std::move(std::as_const(obj))->*crfunc_member)() == 42);
332 static_assert(std::is_same_v<
333 decltype((std::move(std::as_const(obj))->*crfunc_member)()),
334 int>);
335
336 constexpr auto func_member2 = &mem_ptr_test::get_member2;
337 constexpr auto cfunc_member2 = &mem_ptr_test::cget_member2;
338
339 REQUIRE((obj->*func_member2)() == 42);
340 static_assert(std::is_same_v<decltype((obj->*func_member2)()), int>);
341 REQUIRE((obj->*cfunc_member2)() == 42);
342 static_assert(std::is_same_v<decltype((obj->*cfunc_member2)()), int>);
343 REQUIRE((std::as_const(obj)->*cfunc_member2)() == 42);
344 static_assert(
345 std::is_same_v<decltype((std::as_const(obj)->*cfunc_member2)()),
346 int>);
347 }
348}
349
350#endif // KBLIB_USE_CXX17
Inline polymorphic object. Generally mimics the interfaces of std::optional and std::variant.
Definition: poly_obj.h:481
auto has_value() const &noexcept -> bool
Definition: poly_obj.h:712
auto clear() noexcept -> void
Empties the poly_obj, reverting to a default-constructed state.
Definition: poly_obj.h:724
constexpr auto generate_n(OutputIt first, Size count, Generator g) noexcept(noexcept(*first++=g())) -> OutputIt
Like std::generate_n except that it is constexpr.
Definition: algorithm.h:1565
constexpr auto FNV32a(std::string_view s, std::uint32_t hval=fnv::fnv_offset< std::uint32_t >::value) noexcept -> std::uint32_t
A standard FNV32a hash function, for string_views.
Definition: hash.h:194
TEST_CASE("poly_obj")
Definition: poly_obj.cpp:125
Provides poly_obj, which enables polymorphism to be used without unnecessary per-object dynamic alloc...
poly_obj_traits is a traits class template which abstracts the allowed operations on a polymorphic ty...
Definition: poly_obj.h:374
Definition: bits.cpp:9
#define KBLIB_UNUSED
This internal macro is used to provide a fallback for [[maybe_unused]] in C++14.
Definition: tdecl.h:130