out.h 4.5 KB

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