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/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 big_derived()
81 : x(1) {
82
83 x = kblib::FNVa_a<std::size_t>(
84 reinterpret_cast<const char(&)[sizeof(big_derived)]>(*this));
85 }
86 auto bark() const -> void override { bark_log.push_back(vbig_derived); }
87 ~big_derived() noexcept override = default;
88 auto id() const -> int override { return 1; }
89};
90
91struct not_copyable {
92 not_copyable() = default;
93 not_copyable(const not_copyable&) = delete;
94 KBLIB_UNUSED not_copyable(not_copyable&&) noexcept = default;
95 virtual ~not_copyable() = default;
96};
97
98struct copyable_derived : not_copyable {
99 KBLIB_UNUSED copyable_derived() = default;
100 copyable_derived(const copyable_derived&) {}
101 copyable_derived(copyable_derived&&) noexcept = default;
102};
103
104struct copyable_base {
105 virtual ~copyable_base() = default;
106};
107
108struct noncopyable_derived : copyable_base {
109 KBLIB_UNUSED noncopyable_derived() = default;
110 noncopyable_derived(const noncopyable_derived&) = delete;
111 KBLIB_UNUSED noncopyable_derived(noncopyable_derived&&) = default;
112};
113
114struct hinted_base {
115 constexpr static std::size_t max_derived_size = sizeof(big_derived);
116 virtual ~hinted_base() = default;
117};
118
119struct hinted_derived : hinted_base {
120 int member;
121};
122
123template <typename T>
124struct print;
125
126} // namespace
127
128TEST_CASE("poly_obj") {
129 SECTION("basic") {
130 std::vector<barks> expected;
131 bark_log.clear();
132 {
133 kblib::poly_obj<good_base> o1, o2{std::in_place};
134 REQUIRE(not o1.has_value());
135 REQUIRE(o2.has_value());
136 o2->bark(); // good_base
137 expected.push_back(vgood_base);
138 REQUIRE(bark_log == expected);
139
141 // destroyed
142 expected.push_back(dgood_derived);
143 expected.push_back(dgood_base);
144 REQUIRE(bark_log == expected);
145 REQUIRE(o1.has_value());
146
147 o1->bark(); // good_derived
148 expected.push_back(vgood_derived);
149 REQUIRE(bark_log == expected);
150
151 o2 = o1; // o2 ~good_base
152 expected.push_back(dgood_base);
153 REQUIRE(bark_log == expected);
154
155 o2->bark(); // good_derived
156 expected.push_back(vgood_derived);
157 REQUIRE(bark_log == expected);
158
159 o1.clear(); //~good_derived, ~good_base
160 expected.push_back(dgood_derived);
161 expected.push_back(dgood_base);
162 REQUIRE(bark_log == expected);
163 REQUIRE(not o1.has_value());
164 REQUIRE(o2.has_value());
165 // kblib::poly_obj<good_base> o3 =
166 // kblib::poly_obj<good_base>::make<bad_nocopy>();
167 } // o2: ~good_derived, ~good_base
168
169 expected.push_back(dgood_derived);
170 expected.push_back(dgood_base);
171 REQUIRE(bark_log == expected);
172 }
173
174 SECTION("non-virtual destructor") {
175 // static_assertion fails because of non-virtual destructor
176 // kblib::poly_obj<bad_base1> o3, o4{std::in_place};
177 // o3 = kblib::poly_obj<bad_base1>::make<bad_derived1>();
178 }
179
180 SECTION("private base") {
181 std::vector<barks> expected;
182 bark_log.clear();
183 {
184 kblib::poly_obj<bad_base2> o5, o6{std::in_place};
185 // illegal - non-public base
186 // o5 = kblib::poly_obj<bad_base2>::make<bad_derived2>();
187 }
188 expected.push_back(dbad_base2);
189 REQUIRE(bark_log == expected);
190 }
191
192 SECTION("capacity") {
193 std::vector<barks> expected;
194 bark_log.clear();
195
196 // illegal - unrelated types
197 // kblib::poly_obj<good_base>::make<unrelated>();
198 // illegal - derived too big
199 // kblib::poly_obj<small_base>::make<big_derived>();
200 // fine
201 kblib::poly_obj<small_base, sizeof(big_derived)> o7, o8{std::in_place};
202 o8->bark();
203 expected.push_back(vsmall_base);
204 REQUIRE(bark_log == expected);
205 o7 = kblib::poly_obj<small_base,
206 sizeof(big_derived)>::make<big_derived>();
207 REQUIRE(o7.has_value());
208 o7->bark();
209 expected.push_back(vbig_derived);
210 REQUIRE(bark_log == expected);
211 }
212
213 SECTION("non-copyable type") {
214 // A poly_obj of non-copyable type is allowed and non-copyable
215 kblib::poly_obj<not_copyable> o1{std::in_place}, o2;
216 // illegal
217 // o2 = o1;
218 // legal
219 o2 = std::move(o1);
220 // legal
222 // illegal (can't copy even if derived is copyable)
223 // o1 = o3;
224 }
225
226 SECTION("non-copyable derived") {
227 kblib::poly_obj<copyable_base, sizeof(copyable_base),
229 o1{std::in_place};
230 // Valid because the template parameters explicitly disable copying, so
231 // derived classes don't need to be copyable.
232 auto o2 = decltype(o1)::make<noncopyable_derived>();
233 }
234
235 SECTION("mixed vector") {
236 auto r = [n = std::uint16_t{16127}]() mutable {
237 n ^= 19937u;
238 n *= 4889;
239 return kblib::FNV32a({reinterpret_cast<char*>(&n), sizeof(n)});
240 };
241
242 using obj = kblib::poly_obj<small_base, sizeof(big_derived)>;
243
244 std::vector<obj> polyvec;
245 int g[2] = {};
246 std::generate_n(std::back_inserter(polyvec), 128, [&] {
247 if ((r() % 2)) {
248 ++g[1];
249 return obj::make<big_derived>();
250 } else {
251 ++g[0];
252 return obj(std::in_place);
253 }
254 });
255 REQUIRE(g[0] == 64);
256 REQUIRE(g[1] == 64);
257
258 int c[2] = {};
259 // char ab[] = "ab";
260 for (const auto& o : polyvec) {
261 ++c[o->id()];
262 // std::cout << ab[o->id()];
263 }
264 REQUIRE(c[0] == 64);
265 REQUIRE(c[1] == 64);
266 }
267
268 SECTION("free make function") {
269 // Automatically takes the larger capacity value, but returns a base
270 // pointer
271 auto o = kblib::make_poly_obj<small_base, big_derived>();
272 static_assert(
273 std::is_same_v<decltype(o),
274 kblib::poly_obj<small_base, sizeof(big_derived)>>,
275 "make_poly_obj must return the correct type.");
276 }
277 SECTION("hinted") {
279 == hinted_base::max_derived_size);
281 static_assert(decltype(o)::capacity
282 == decltype(o)::base_type::max_derived_size);
283 o = decltype(o)::make<hinted_derived>();
284 }
285}
286
287namespace {
288struct mem_ptr_test {
289 int member{42};
290 auto get_member() & noexcept -> int { return member; }
291 auto cget_member() const& noexcept -> int { return member; }
292 auto rget_member() && noexcept -> int { return member; }
293 auto crget_member() const&& noexcept -> int { return member; }
294
295 auto get_member2() noexcept -> int { return member; }
296 auto cget_member2() const noexcept -> int { return member; }
297
298 virtual ~mem_ptr_test() = default;
299};
300} // namespace
301
302TEST_CASE("poly_obj->*") {
303 kblib::poly_obj<mem_ptr_test> obj{std::in_place};
304 SECTION("member data") {
305 constexpr auto data_member = &mem_ptr_test::member;
306 REQUIRE(obj->*data_member == 42);
307 static_assert(std::is_same_v<decltype(obj->*data_member), int&>);
308 static_assert(std::is_same_v<decltype(std::as_const(obj)->*data_member),
309 const int&>);
310 static_assert(
311 std::is_same_v<decltype(std::move(obj)->*data_member), int&&>);
312 static_assert(
313 std::is_same_v<decltype(std::move(std::as_const(obj))->*data_member),
314 const int&&>);
315 }
316 SECTION("member function") {
317 constexpr auto func_member = &mem_ptr_test::get_member;
318 constexpr auto cfunc_member = &mem_ptr_test::cget_member;
319 constexpr auto rfunc_member = &mem_ptr_test::rget_member;
320 constexpr auto crfunc_member = &mem_ptr_test::crget_member;
321 REQUIRE((obj->*func_member)() == 42);
322 static_assert(std::is_same_v<decltype((obj->*func_member)()), int>);
323 REQUIRE((obj->*cfunc_member)() == 42);
324 static_assert(std::is_same_v<decltype((obj->*cfunc_member)()), int>);
325 REQUIRE((std::as_const(obj)->*cfunc_member)() == 42);
326 static_assert(
327 std::is_same_v<decltype((std::as_const(obj)->*cfunc_member)()), int>);
328 REQUIRE((std::move(obj)->*rfunc_member)() == 42);
329 static_assert(
330 std::is_same_v<decltype((std::move(obj)->*rfunc_member)()), int>);
331 REQUIRE((std::move(obj)->*crfunc_member)() == 42);
332 static_assert(
333 std::is_same_v<decltype((std::move(obj)->*crfunc_member)()), int>);
334 REQUIRE((std::move(std::as_const(obj))->*crfunc_member)() == 42);
335 static_assert(std::is_same_v<
336 decltype((std::move(std::as_const(obj))->*crfunc_member)()),
337 int>);
338
339 constexpr auto func_member2 = &mem_ptr_test::get_member2;
340 constexpr auto cfunc_member2 = &mem_ptr_test::cget_member2;
341
342 REQUIRE((obj->*func_member2)() == 42);
343 static_assert(std::is_same_v<decltype((obj->*func_member2)()), int>);
344 REQUIRE((obj->*cfunc_member2)() == 42);
345 static_assert(std::is_same_v<decltype((obj->*cfunc_member2)()), int>);
346 REQUIRE((std::as_const(obj)->*cfunc_member2)() == 42);
347 static_assert(
348 std::is_same_v<decltype((std::as_const(obj)->*cfunc_member2)()),
349 int>);
350 }
351}
352
353#endif // KBLIB_USE_CXX17
#define SECTION(...)
Definition: catch.hpp:17716
#define REQUIRE(...)
Definition: catch.hpp:17676
Inline polymorphic object. Generally mimics the interfaces of std::optional and std::variant.
Definition: poly_obj.h:478
auto has_value() const &noexcept -> bool
Definition: poly_obj.h:736
auto clear() noexcept -> void
Empties the poly_obj, reverting to a default-constructed state.
Definition: poly_obj.h:748
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:128
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:371
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:147