| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- //
- // out.hpp
- // pointer
- //
- // Created by Sam Jaffe on 11/4/23.
- //
- //
- #pragma once
- #include <type_traits>
- #include <utility>
- #include <variant>
- 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 <typename T> class out {
- private:
- static_assert(std::is_same_v<T, std::decay_t<T>>,
- "out<T> 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 <typename U,
- typename = std::enable_if_t<std::is_constructible_v<T, U>>>
- void operator=(U && value) {
- if (ref_) { *ref_ = T(std::forward<U>(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 <typename T> class inout {
- private:
- static_assert(std::is_same_v<T, std::decay_t<T>>,
- "inout<T> can only be value-types");
- std::variant<T, T *> 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 <typename U>
- requires std::is_constructible_v<T, U>
- T const & operator=(U && val) {
- struct {
- U && val;
- T const & operator()(T & in) const { return in = std::forward<U>(val); }
- T const & operator()(T * in) const {
- return *in = std::forward<U>(val);
- }
- } visitor{std::forward<U>(val)};
- return std::visit(visitor, ref_);
- }
- };
- }
|