// Copyright (C) 2023 johannst #include #include #include #include #include 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 struct option { static_assert(!std::is_reference_v); constexpr option() = default; template 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)...); } ~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(m_val)); } private: constexpr void reset() { if (!m_has_val) { return; } if constexpr (!std::is_trivially_destructible_v) { 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 opt1(S{}, 123); std::puts("==> case 2"); // invokes M() + M(M&&) option x /* option(M&&) + M(M&&) */ = M{} /* M() */; }