// // out.hpp // pointer // // Created by Sam Jaffe on 11/4/23. // // #pragma once #include #include #include namespace pointers { constexpr struct discard_out_t { } discard_out; /** * @brief An optional out-parameter to a function, similar to a function * that takes `T* out_param = nullptr`. Unfortunately, std::optional does not * support storing references - so if we want the syntactic sugar of that, we * need a custom class. * * In addition to acting like an optional value - we have the special behavior * that we wanted - namely that "if there is a contained value provided by the * caller, we will update that reference", and "if there is no value, then * assigning a value will have no effect" as if we performed the idiomatic: * @code * if (out_param) { * *out_param = ...; * } * @endcode * * @tparam T The type being returned */ template class out { private: static_assert(std::is_same_v>, "out can only be value-types"); T * ref_ = nullptr; public: // Explicitly construct an empty out parameter, that has no side-effects out(discard_out_t) {} // NOLINT NOSONAR // Construct an out parameter pointing to the given concrete value. out(T & value) : impl_(&value) {} // NOLINT NOSONAR /** * @breif On rare occasions, we still need to perform checks * that an out-param holds a value. */ explicit operator bool() const { return impl_; } /** * @brief Update the value of this out parameter, if it holds a value. By * convention, we assume that this function will only be called once, but * there is no requirement for that. * * @tparam U Any type that can be used to construct the held type T * * @param val The new value being passed up to the caller * * @returns Nothing - this object does not behave like a normal object where * you can do things like `if ((A = B).foo())` - since this object * represents exclusively a way to pass an optional value back to the caller * without returning a tuple. */ template >> void operator=(U && value) { if (ref_) { *ref_ = T(std::forward(value)); } return *this; } }; /** * @brief A non-optional out-parameter to a function, similar to a function * that takes a `T& out_param` argument. Unlike the standard form, this type * allows passing an rvalue (temporary) or an lvalue (persistant) element, and * will properly handle the assigment and updating of the object as * appropriate. * * @tparam T The type being returned */ template class inout { private: static_assert(std::is_same_v>, "inout can only be value-types"); std::variant ref_; public: // Constructs an inout parameter from an rvalue type - whose modification // will not effect the calling scope. inout(T && value) : ref_(std::move(value)) {} // Constructs an inout parameter from an lvalue type - whose modification // will propogate upwards to the caller. inout(T & ref) : ref_(&ref) {} /** * @brief Convert this object back into its underlying type for use. * * @returns A reference to the contained value/reference, to avoid the cost * of performing a copy-operation if the contained object is non-trivial. */ operator T const &() const { struct { T const & operator()(T const & in) const { return in; } T const & operator()(T * in) const { return *in; } } visitor; return std::visit(visitor, ref_); } /** * @brief Update the value of this out parameter. Depending on the variation * contained in this type, this will either propogate up to the caller's * level or will update future uses of this object. * * @tparam U Any type that can be used to construct the held type T * * @param val The new value being set * * @returns The updated value */ template requires std::is_constructible_v T const & operator=(U && val) { struct { U && val; T const & operator()(T & in) const { return in = std::forward(val); } T const & operator()(T * in) const { return *in = std::forward(val); } } visitor{std::forward(val)}; return std::visit(visitor, ref_); } }; }