| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- #pragma once
- #include <type_traits>
- #include <utility>
- #include <variant>
- namespace jvalidate::detail {
- 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>
- requires(std::is_same_v<T, std::decay_t<T>>)
- class out {
- private:
- T * ref_ = nullptr;
- public:
- // Construct an empty out parameter, that has no side-effects
- out() = default;
- // Explicitly construct an empty out parameter, that has no side-effects
- out(discard_out_t) {}
- // Construct an out parameter pointing to the given concrete value.
- out(T & ref) : ref_(&ref) {}
- /**
- * @breif On rare occasions, we still need to perform checks
- * that an out-param holds a value.
- */
- explicit operator bool() const { return ref_; }
- /**
- * @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>
- requires std::is_constructible_v<T, U>
- void operator=(U && val) {
- if (ref_) {
- *ref_ = std::forward<U>(val);
- }
- 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>
- requires(std::is_same_v<T, std::decay_t<T>>)
- class inout {
- private:
- 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_);
- }
- };
- }
|