Skip to content

Commit 8972b04

Browse files
committed
[expected] fix move ctor of <void, E>, add emplace to all
1 parent 9e944c8 commit 8972b04

File tree

2 files changed

+129
-16
lines changed

2 files changed

+129
-16
lines changed

include/itlib/expected.hpp

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// itlib-expected v1.03
1+
// itlib-expected v1.04
22
//
33
// A union-type of a value and an error
44
//
@@ -28,6 +28,8 @@
2828
//
2929
// VERSION HISTORY
3030
//
31+
// 1.04 (2025-04-03) - Fix move assign in <void, E> specialization
32+
// - Add emplace for all specializations
3133
// 1.03 (2025-01-23) Add value and error "getters" in void specializations
3234
// 1.02 (2022-09-02) Specializations for ref and void values and void errors
3335
// 1.01 (2021-09-27) Fixed value_or which could return a ref to temporary
@@ -198,11 +200,11 @@ class expected
198200
{
199201
if (m_has_value)
200202
{
201-
new (&m_value) T(std::move(other.m_value));
203+
::new (&m_value) T(std::move(other.m_value));
202204
}
203205
else
204206
{
205-
new (&m_error) E(std::move(other.m_error));
207+
::new (&m_error) E(std::move(other.m_error));
206208
}
207209
}
208210

@@ -243,6 +245,22 @@ class expected
243245
}
244246
}
245247

248+
template <typename... Args>
249+
T& emplace(Args&&... args)
250+
{
251+
if (m_has_value)
252+
{
253+
m_value.~T();
254+
}
255+
else
256+
{
257+
m_error.~E();
258+
}
259+
::new (&m_value) T(std::forward<Args>(args)...);
260+
m_has_value = true;
261+
return m_value;
262+
}
263+
246264
// bool interface
247265
bool has_value() const { return m_has_value; }
248266
bool has_error() const { return !m_has_value; }
@@ -366,6 +384,16 @@ class expected<T&, E> {
366384
}
367385
}
368386

387+
T& emplace(T& t)
388+
{
389+
if (!m_value)
390+
{
391+
m_error.~E();
392+
}
393+
m_value = &t;
394+
return t;
395+
}
396+
369397
// bool interface
370398
bool has_value() const { return !!m_value; }
371399
bool has_error() const { return !m_value; }
@@ -447,10 +475,11 @@ class expected<void, E> {
447475
m_has_value = true;
448476
m_error.~E();
449477
}
450-
else
478+
else if (!m_has_value && !other.has_value())
451479
{
452480
m_error = std::move(other.m_error);
453481
}
482+
// else nothing to do
454483
return *this;
455484
}
456485

@@ -462,6 +491,15 @@ class expected<void, E> {
462491
}
463492
}
464493

494+
void emplace()
495+
{
496+
if (!m_has_value)
497+
{
498+
m_error.~E();
499+
}
500+
m_has_value = true;
501+
}
502+
465503
// bool interface
466504
bool has_value() const { return m_has_value; }
467505
bool has_error() const { return !m_has_value; }
@@ -517,7 +555,7 @@ class expected<T, void>
517555
{
518556
if (m_has_value)
519557
{
520-
new (&m_value) T(other.m_value);
558+
::new (&m_value) T(other.m_value);
521559
}
522560
}
523561
expected& operator=(const expected& other)
@@ -544,7 +582,7 @@ class expected<T, void>
544582
{
545583
if (m_has_value)
546584
{
547-
new (&m_value) T(std::move(other.m_value));
585+
::new (&m_value) T(std::move(other.m_value));
548586
}
549587
}
550588

@@ -588,11 +626,12 @@ class expected<T, void>
588626
m_has_value = false;
589627
}
590628
template <typename... Args>
591-
void emplace(Args&&... args)
629+
T& emplace(Args&&... args)
592630
{
593631
clear();
594632
::new (&m_value) T(std::forward<Args>(args)...);
595633
m_has_value = true;
634+
return m_value;
596635
}
597636

598637
// value getters
@@ -661,7 +700,11 @@ class expected<T&, void> {
661700

662701
// optional interface
663702
void clear() { m_value = nullptr; }
664-
void emplace(T& t) { m_value = &t; }
703+
T& emplace(T& t)
704+
{
705+
m_value = &t;
706+
return t;
707+
}
665708

666709
// value getters (pointer semantics)
667710
T& value() const

test/t-expected-11.cpp

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,23 @@ TEST_CASE("lifetime")
220220
CHECK(vs.living == 1);
221221
}
222222

223+
// error to value 2
224+
{
225+
value::lifetime_stats vs;
226+
error::lifetime_stats es;
227+
auto x = func(false);
228+
auto& ref = x.emplace(8);
229+
CHECK(ref.val == 8);
230+
CHECK(x.has_value());
231+
CHECK(es.d_ctr == 1);
232+
CHECK(es.total == 1);
233+
CHECK(es.living == 0);
234+
CHECK(vs.d_ctr == 1);
235+
CHECK(vs.m_ctr == 0);
236+
CHECK(vs.total == 1);
237+
CHECK(vs.living == 1);
238+
}
239+
223240
// value to error
224241
{
225242
value::lifetime_stats vs;
@@ -235,6 +252,20 @@ TEST_CASE("lifetime")
235252
CHECK(es.total == 2);
236253
CHECK(es.living == 1);
237254
}
255+
256+
// value to value
257+
{
258+
value::lifetime_stats vs;
259+
error::lifetime_stats es;
260+
auto x = func(true);
261+
auto& ref = x.emplace(105);
262+
CHECK(ref.val == 105);
263+
CHECK(x.has_value());
264+
CHECK(vs.d_ctr == 2);
265+
CHECK(vs.total == 2);
266+
CHECK(vs.living == 1);
267+
CHECK(es.checkpoint() == empty);
268+
}
238269
}
239270

240271
struct obj {
@@ -374,6 +405,25 @@ TEST_CASE("ref lifetime")
374405
CHECK(vs.living == 1);
375406
}
376407

408+
// error to value 2
409+
{
410+
value::lifetime_stats vs;
411+
error::lifetime_stats es;
412+
const obj o;
413+
414+
auto x = o.ie();
415+
value v;
416+
auto& ref = x.emplace(v);
417+
CHECK(&ref == &v);
418+
419+
CHECK(x.has_value());
420+
CHECK(es.d_ctr == 1);
421+
CHECK(es.total == 1);
422+
CHECK(es.living == 0);
423+
CHECK(vs.total == 2);
424+
CHECK(vs.living == 2);
425+
}
426+
377427
// value to error
378428
{
379429
value::lifetime_stats vs;
@@ -420,6 +470,9 @@ TEST_CASE("void basic")
420470
CHECK(!v);
421471
REQUIRE(v.has_error());
422472
CHECK(v.error().err == 1);
473+
474+
v.emplace();
475+
CHECK(v.has_value());
423476
}
424477

425478
TEST_CASE("void lifetime")
@@ -443,23 +496,23 @@ TEST_CASE("void lifetime")
443496
{
444497
error::lifetime_stats es;
445498

446-
const auto x = func(true);
499+
const auto x = vfunc(true);
447500
CHECK(es.checkpoint() == empty);
448501
}
449502

450503
{
451504
error::lifetime_stats es;
452505

453-
auto x = func(false);
454-
x = func(false);
506+
auto x = vfunc(false);
507+
x = vfunc(false);
455508
REQUIRE(x.has_error());
456509
CHECK(x.error().err == 1);
457510
CHECK(es.d_ctr == 2);
458511
CHECK(es.m_ctr == 0);
459512
CHECK(es.m_asgn == 1);
460513
CHECK(es.living == 1);
461514

462-
auto e = func(false).error();
515+
auto e = vfunc(false).error();
463516
CHECK(es.d_ctr == 3);
464517
CHECK(es.m_ctr == 1);
465518
CHECK(es.m_asgn == 1);
@@ -470,8 +523,8 @@ TEST_CASE("void lifetime")
470523
{
471524
error::lifetime_stats es;
472525

473-
auto x = func(true);
474-
x = func(true);
526+
auto x = vfunc(true);
527+
x = vfunc(true);
475528
CHECK(es.checkpoint() == empty);
476529
}
477530

@@ -487,6 +540,17 @@ TEST_CASE("void lifetime")
487540
CHECK(es.living == 0);
488541
}
489542

543+
// error to value 2
544+
{
545+
error::lifetime_stats es;
546+
auto x = func(false);
547+
x.emplace();
548+
CHECK(x.has_value());
549+
CHECK(es.d_ctr == 1);
550+
CHECK(es.total == 1);
551+
CHECK(es.living == 0);
552+
}
553+
490554
// value to error
491555
{
492556
error::lifetime_stats es;
@@ -530,10 +594,13 @@ TEST_CASE("eoptional")
530594
CHECK(io.value_or(44) == 44);
531595
io.error(); // should compile and safely do nothing
532596

533-
io.emplace(5);
597+
auto& ref = io.emplace(5);
534598
CHECK(io);
535599
CHECK(*io == 5);
536600
CHECK(io.value_or(44) == 5);
601+
CHECK(ref == 5);
602+
ref = 10;
603+
CHECK(io.value_or(44) == 10);
537604
}
538605

539606
TEST_CASE("eoptional ref")
@@ -564,10 +631,13 @@ TEST_CASE("eoptional ref")
564631
CHECK(io.value_or(44) == 44);
565632

566633
int i = 34;
567-
io.emplace(i);
634+
auto& ref = io.emplace(i);
568635
CHECK(io);
569636
CHECK(*io == 34);
570637
CHECK(&io.value() == &i);
638+
CHECK(ref == 34);
639+
i = 10;
640+
CHECK(ref == 10);
571641
}
572642

573643
TEST_CASE("eoptional void")

0 commit comments

Comments
 (0)