|
|
@@ -0,0 +1,138 @@
|
|
|
+//
|
|
|
+// 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_);
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|