out.h 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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. }
  64. };
  65. /**
  66. * @brief A non-optional out-parameter to a function, similar to a function that
  67. * takes a `T& out_param` argument. Unlike the standard form, this type allows
  68. * passing an rvalue (temporary) or an lvalue (persistant) element, and will
  69. * properly handle the assigment and updating of the object as appropriate.
  70. *
  71. * @tparam T The type being returned
  72. */
  73. template <typename T>
  74. requires(std::is_same_v<T, std::decay_t<T>>)
  75. class inout {
  76. private:
  77. std::variant<T, T *> ref_;
  78. public:
  79. // Constructs an inout parameter from an rvalue type - whose modification will
  80. // not effect the calling scope.
  81. inout(T && value) : ref_(std::move(value)) {}
  82. // Constructs an inout parameter from an lvalue type - whose modification will
  83. // propogate upwards to the caller.
  84. inout(T & ref) : ref_(&ref) {}
  85. /**
  86. * @brief Convert this object back into its underlying type for use.
  87. *
  88. * @returns A reference to the contained value/reference, to avoid the cost
  89. * of performing a copy-operation if the contained object is non-trivial.
  90. */
  91. operator T const &() const {
  92. struct {
  93. T const & operator()(T const & in) const { return in; }
  94. T const & operator()(T * in) const { return *in; }
  95. } visitor;
  96. return std::visit(visitor, ref_);
  97. }
  98. /**
  99. * @brief Update the value of this out parameter. Depending on the variation
  100. * contained in this type, this will either propogate up to the caller's level
  101. * or will update future uses of this object.
  102. *
  103. * @tparam U Any type that can be used to construct the held type T
  104. *
  105. * @param val The new value being set
  106. *
  107. * @returns The updated value
  108. */
  109. template <typename U>
  110. requires std::is_constructible_v<T, U>
  111. T const & operator=(U && val) {
  112. struct {
  113. U && val;
  114. T const & operator()(T & in) const { return in = std::forward<U>(val); }
  115. T const & operator()(T * in) const { return *in = std::forward<U>(val); }
  116. } visitor{std::forward<U>(val)};
  117. return std::visit(visitor, ref_);
  118. }
  119. };
  120. }