// Released (₡) 2019 by Adam C. Emerson // // To the extent possible under law, the author has dedicated all // copyright and related and neighboring rights to this software to // the public domain worldwide. This software is distributed without // any warranty. // // You should have received a copy of the CC0 Public Domain Dedication // along with this software. If not, see // . #ifndef FLUID_HH #define FLUID_HH #include #include #include #include namespace _fluid { template()> struct default_construct; template struct default_construct { static T* if_i_can(void* space) { return ::new (space) T; } }; template struct default_construct { static T* if_i_can(void*) { // Apparently I can't. return nullptr; } }; } // `empty_fluid` // ------------- // // An exception to be thrown if you dereference an empty `fluid`. This // is a logic error because you should know better. // struct empty_fluid : public ::std::logic_error { explicit empty_fluid(const char* what) : logic_error(what) {} explicit empty_fluid(const ::std::string& what) : logic_error(what) {} }; namespace _fluid { template class base; // // If we can be empty, we must check that we are not and throw // exceptions if we are. // template class base { void throw_if_empty() const { if (empty()) throw empty_fluid("You cannot access an empty fluid."); } protected: T* ref; public: base(T* ref) noexcept : ref(ref) {} base(const base&) = delete; base& operator =(const base&) = delete; base(base&&) = delete; base& operator =(base&&) = delete; bool empty() const noexcept { return !ref; } T& operator *() const { throw_if_empty(); return *ref; } operator T&() const { return *(*this); } T* operator ->() const { throw_if_empty(); return ref; } }; // // If we know we'll never be empty, don't waste time checking. // template class base { protected: T* ref; public: base(T* ref) noexcept : ref(ref) { } base(const base&) = delete; base& operator =(const base&) = delete; base(base&&) = delete; base& operator =(base&&) = delete; bool empty() const noexcept { return false; } T& operator *() const noexcept { return *ref; } operator T&() const noexcept { return *ref; } T* operator ->() const noexcept { return ref; } }; } // // Just a forward declaration. This way the narrative flow of // the sourcecode works better. // template class fluidly_let; template class fluid : public _fluid::base()> { friend class fluidly_let; ::std::aligned_storage_t initial; using base = _fluid::base()>; public: using value_type = T; fluid() noexcept(::std::is_nothrow_default_constructible()) : base(_fluid::default_construct::if_i_can(&initial)) {} template explicit fluid(Args&& ...args) noexcept(noexcept(T(::std::forward(args)...))) : base(::new (&initial) T(::std::forward(args)...)) {} ~fluid() { // // This is me being nice to you. If this test fails it // means you are bad and wrong and ought not destroy the // `fluid` when we're five miles down the stack with a few // hundred `fluidly_let`s to unmake before we go. What did // you do, dynamically allocate it and call delete? Why // would you do that? // assert(static_cast(base::ref) == reinterpret_cast(&initial) || static_cast(base::ref) == nullptr); // // The downside of placement new // if (!base::empty()) { base::ref->~T(); } } }; template class fluid : public _fluid::base { friend fluidly_let; using base = _fluid::base; public: using value_type = T&; fluid() noexcept : base(nullptr) {} explicit fluid(T& v) noexcept : base(::std::addressof(v)) {} }; template class fluidly_let { fluid& f; // This is the value that we set. T val; // And this is the old value. T* old; public: template fluidly_let(fluid& f, Args&& ...args) noexcept(noexcept(T(::std::forward(args)...))) : f(f), val(::std::forward(args)...), old(f.ref) { f.ref = ::std::addressof(val); } ~fluidly_let() { f.ref = old; } fluidly_let(const fluidly_let&) = delete; fluidly_let& operator =(const fluidly_let&) = delete; fluidly_let(fluidly_let&&) = delete; fluidly_let& operator =(fluidly_let&&) = delete; }; // // Another version for references. References are special // since you can't have a pointer to them. // template class fluidly_let { fluid& f; // The previous resident in the fluid T* old; public: fluidly_let(fluid& f, T& val) noexcept : f(f), old(f.ref) { f.ref = ::std::addressof(val); } ~fluidly_let() { f.ref = old; } fluidly_let(const fluidly_let&) = delete; fluidly_let& operator =(const fluidly_let&) = delete; fluidly_let(fluidly_let&&) = delete; fluidly_let& operator =(fluidly_let&&) = delete; }; #endif // FLUID_HH