aboutsummaryrefslogtreecommitdiffhomepage
path: root/development/c++/fwd-perfect.cc
blob: 15bf2b4dd9fe732c91b1aa2a9980843200cd7e7f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Copyright (C) 2023 johannst

#include <cassert>
#include <cstdio>
#include <new>
#include <type_traits>
#include <utility>

struct S {};

struct M {
    M() {
        std::puts("M()");
    }
    M(const M&) {
        std::puts("M(M&)");
    }
    M(M&&) {
        std::puts("M(M&&)");
    }
    M& operator=(const M&) = delete;
    M& operator=(M&&) = delete;

    M(S&, int) {
        std::puts("M(S&)");
    }
    M(S&&, int) {
        std::puts("M(S&&)");
    }
    ~M() {
        std::puts("~M()");
    }
};

template<typename T>
struct option {
    static_assert(!std::is_reference_v<T>);

    constexpr option() = default;

    template<typename... Params>
    constexpr option(Params&&... params) : m_has_val(true) {
        // BAD: does not perfectly forward!
        //      eg, if option(S&&) is invoked, this would invoke M(S&).
        // new (&m_val) T(params...);

        // GOOD: perfectly forwards params to constructor of T.
        new (m_val) T(std::forward<Params>(params)...);
    }

    ~option() {
        reset();
    }

    constexpr T& value() {
        assert(m_has_val);
        // Placement new starts a new lifetime, launder pointer returned to the
        // aligned storage.
        //
        // [1] https://en.cppreference.com/w/cpp/utility/launder
        return *__builtin_launder(reinterpret_cast<T*>(m_val));
    }

  private:
    constexpr void reset() {
        if (!m_has_val) {
            return;
        }
        if constexpr (!std::is_trivially_destructible_v<T>) {
            value().~T();
        };
    }

    alignas(T) char m_val[sizeof(T)];
    bool m_has_val{false};
};

int main() {
    std::puts("==> case 1");
    // invokes M(S&&, int)
    option<M> opt1(S{}, 123);

    std::puts("==> case 2");
    // invokes M() + M(M&&)
    option<M> x /* option(M&&) + M(M&&) */ = M{} /* M() */;
}