out.hpp 4.5 KB

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