out.h 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #pragma once
  2. #include <type_traits>
  3. #include <utility>
  4. #include <variant>
  5. namespace jvalidate::detail {
  6. constexpr struct discard_out_t {
  7. } discard_out;
  8. /**
  9. * @brief An optional out-parameter to a function, similar to a function
  10. * that takes `T* out_param = nullptr`. Unfortunately, std::optional does not
  11. * support storing references - so if we want the syntactic sugar of that, we
  12. * need a custom class.
  13. *
  14. * In addition to acting like an optional value - we have the special behavior
  15. * that we wanted - namely that "if there is a contained value provided by the
  16. * caller, we will update that reference", and "if there is no value, then
  17. * assigning a value will have no effect" as if we performed the idiomatic:
  18. * @code
  19. * if (out_param) {
  20. * *out_param = ...;
  21. * }
  22. * @endcode
  23. *
  24. * @tparam T The type being returned
  25. */
  26. template <typename T>
  27. requires(std::is_same_v<T, std::decay_t<T>>)
  28. class out {
  29. private:
  30. T * ref_ = nullptr;
  31. public:
  32. // Construct an empty out parameter, that has no side-effects
  33. out() = default;
  34. // Explicitly construct an empty out parameter, that has no side-effects
  35. out(discard_out_t) {}
  36. // Construct an out parameter pointing to the given concrete value.
  37. out(T & ref) : ref_(&ref) {}
  38. /**
  39. * @breif On rare occasions, we still need to perform checks
  40. * that an out-param holds a value.
  41. */
  42. explicit operator bool() const { return ref_; }
  43. /**
  44. * @brief Update the value of this out parameter, if it holds a value. By
  45. * convention, we assume that this function will only be called once, but
  46. * there is no requirement for that.
  47. *
  48. * @tparam U Any type that can be used to construct the held type T
  49. *
  50. * @param val The new value being passed up to the caller
  51. *
  52. * @returns Nothing - this object does not behave like a normal object where
  53. * you can do things like `if ((A = B).foo())` - since this object represents
  54. * exclusively a way to pass an optional value back to the caller without
  55. * returning a tuple.
  56. */
  57. template <typename U>
  58. requires std::is_constructible_v<T, U>
  59. void operator=(U && val) {
  60. if (ref_) {
  61. *ref_ = std::forward<U>(val);
  62. }
  63. return *this;
  64. }
  65. };
  66. /**
  67. * @brief A non-optional out-parameter to a function, similar to a function that
  68. * takes a `T& out_param` argument. Unlike the standard form, this type allows
  69. * passing an rvalue (temporary) or an lvalue (persistant) element, and will
  70. * properly handle the assigment and updating of the object as appropriate.
  71. *
  72. * @tparam T The type being returned
  73. */
  74. template <typename T>
  75. requires(std::is_same_v<T, std::decay_t<T>>)
  76. class inout {
  77. private:
  78. std::variant<T, T *> ref_;
  79. public:
  80. // Constructs an inout parameter from an rvalue type - whose modification will
  81. // not effect the calling scope.
  82. inout(T && value) : ref_(std::move(value)) {}
  83. // Constructs an inout parameter from an lvalue type - whose modification will
  84. // propogate upwards to the caller.
  85. inout(T & ref) : ref_(&ref) {}
  86. /**
  87. * @brief Convert this object back into its underlying type for use.
  88. *
  89. * @returns A reference to the contained value/reference, to avoid the cost
  90. * of performing a copy-operation if the contained object is non-trivial.
  91. */
  92. operator T const &() const {
  93. struct {
  94. T const & operator()(T const & in) const { return in; }
  95. T const & operator()(T * in) const { return *in; }
  96. } visitor;
  97. return std::visit(visitor, ref_);
  98. }
  99. /**
  100. * @brief Update the value of this out parameter. Depending on the variation
  101. * contained in this type, this will either propogate up to the caller's level
  102. * or will update future uses of this object.
  103. *
  104. * @tparam U Any type that can be used to construct the held type T
  105. *
  106. * @param val The new value being set
  107. *
  108. * @returns The updated value
  109. */
  110. template <typename U>
  111. requires std::is_constructible_v<T, U>
  112. T const & operator=(U && val) {
  113. struct {
  114. U && val;
  115. T const & operator()(T & in) const { return in = std::forward<U>(val); }
  116. T const & operator()(T * in) const { return *in = std::forward<U>(val); }
  117. } visitor{std::forward<U>(val)};
  118. return std::visit(visitor, ref_);
  119. }
  120. };
  121. }