expect.hpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. //
  2. // require.hpp
  3. // gameutils
  4. //
  5. // Created by Sam Jaffe on 8/19/16.
  6. //
  7. #pragma once
  8. #include <functional>
  9. #include <stdexcept>
  10. #include <string>
  11. #undef CONCAT2
  12. #define CONCAT2(A, B) A##B
  13. #undef CONCAT
  14. #define CONCAT(A, B) CONCAT2(A, B)
  15. #undef STRING2
  16. #define STRING2(A) #A
  17. #undef STRING
  18. #define STRING(A) STRING2(A)
  19. /* expands to the first argument */
  20. #define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
  21. #define FIRST_HELPER(first, ...) first
  22. #define NUM(...) SELECT_5TH(__VA_ARGS__, FOURPLUS, THREE, TWO, ONE, throwaway)
  23. #define SELECT_5TH(a1, a2, a3, a4, a5, ...) a5
  24. namespace contract {
  25. template <typename expect>
  26. class ensure_t {
  27. private:
  28. std::function<bool()> passes_;
  29. std::string message_;
  30. #if __cpp_lib_uncaught_exceptions >= 201411L
  31. int uncaught_exceptions_;
  32. #endif
  33. public:
  34. #if __cpp_lib_uncaught_exceptions >= 201411L
  35. template <typename F>
  36. ensure_t(F && passes, std::string const & msg, char const * = "")
  37. : passes_(passes), message_(msg), uncaught_exceptions_(std::uncaught_exceptions()) {}
  38. ~ensure_t() noexcept(false) {
  39. if (std::uncaught_exceptions() > uncaught_exceptions_ && !passes_()) {
  40. throw expect{message_};
  41. }
  42. }
  43. #else
  44. template <typename F>
  45. ensure_t(F && passes, std::string const & msg, char const * = "")
  46. : passes_(passes), message_(msg) {}
  47. ~ensure_t() noexcept(false) {
  48. if (!std::uncaught_exception() && !passes_()) {
  49. throw expect{message_};
  50. }
  51. }
  52. #endif
  53. };
  54. template <typename except>
  55. void _contract_impl(bool expr, std::string const & msg, char const * = "") {
  56. if ( ! expr ) throw except{ msg };
  57. }
  58. template <typename except>
  59. void _contract_impl(bool expr, char const * msg, char const * = "") {
  60. if ( ! expr ) throw except{ msg };
  61. }
  62. }
  63. #define EXCEPT_T_TWO(X, Y) Y
  64. #define EXCEPT_T_THREE(X, Y, Z) Z
  65. #define EXCEPT_T_FOURPLUS(X, Y, Z, ...) Y
  66. #define EXCEPT_T(...) EXCEPT_T_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
  67. #define EXCEPT_T_HELPER(N, ...) EXCEPT_T_HELPER2(N, __VA_ARGS__)
  68. #define EXCEPT_T_HELPER2(N, ...) EXCEPT_T_##N(__VA_ARGS__)
  69. #define EXCEPT_MSG_TWO(X, Y) Y
  70. #define EXCEPT_MSG_THREE(X, Y, Z) Y
  71. #define EXCEPT_MSG_FOURPLUS(X, Y, Z, ...) Z
  72. #define EXCEPT_MSG(...) EXCEPT_MSG_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
  73. #define EXCEPT_MSG_HELPER(N, ...) EXCEPT_MSG_HELPER2(N, __VA_ARGS__)
  74. #define EXCEPT_MSG_HELPER2(N, ...) EXCEPT_MSG_##N(__VA_ARGS__)
  75. #define LOCATION_MSG ". in " __FILE__ ":" STRING( __LINE__ )
  76. #define DEF_MSG( header, expr ) \
  77. header " failed: " STRING(expr) LOCATION_MSG
  78. #define SUB_MSG( header, bool_expr, rval_expr ) \
  79. header " failed: " STRING(bool_expr) " [ with _ = " STRING(rval_expr) " ]" LOCATION_MSG
  80. /*
  81. * Usage:
  82. * expects( bool-expr )
  83. * expects( bool-expr, custom_msg )
  84. * expects( bool-expr, error_type, custom_msg )
  85. */
  86. #define expects( ... ) \
  87. contract::_contract_impl<EXCEPT_T( __VA_ARGS__, std::logic_error)>( \
  88. FIRST(__VA_ARGS__), \
  89. EXCEPT_MSG(__VA_ARGS__, \
  90. DEF_MSG("precondition", FIRST(__VA_ARGS__))) \
  91. )
  92. /*
  93. * Usage:
  94. * expects_graceful( bool-expr[, rval] )
  95. *
  96. * rval - The value to return if the expression is false.
  97. * Skip rval for a function returning void.
  98. */
  99. #define expects_graceful( expr, ... ) \
  100. if ( ! bool(expr) ) { return __VA_ARGS__; }
  101. /*
  102. * Usage:
  103. * ensures( bool-expr )
  104. * ensures( bool-expr, custom_msg )
  105. * ensures( bool-expr, error_type, custom_msg )
  106. */
  107. #define ensures( ... ) \
  108. contract::ensure_t<EXCEPT_T(__VA_ARGS__, std::runtime_error)>\
  109. CONCAT(contract_ensures_, __LINE__)( \
  110. [&]() { return FIRST(__VA_ARGS__); }, \
  111. EXCEPT_MSG(__VA_ARGS__ , \
  112. DEF_MSG("postcondition", FIRST(__VA_ARGS__))) \
  113. )
  114. /**
  115. * RVO works if expr is an rvalue, but not if it is an
  116. * lvalue. e.g:
  117. * return ensures( expression, condition );
  118. * will rvo the object (if expression is rvo-able)
  119. * but
  120. * return ensures( var, condition );
  121. * will not, instead copies
  122. * By making _ an argument, both options will move
  123. *
  124. * Usage:
  125. * return_ensures( rval-expr, ensure-expr )
  126. * return_ensures( rval-expr, ensure-expr, custom_msg )
  127. * return_ensures( rval-expr, ensure-expr, error_type, custom_msg )
  128. *
  129. * rval-expr - An expression that can be cast to the return type
  130. * of the invoking function. Bound to the token '_'.
  131. * ensure-expr - A special boolean expression using the token '_'.
  132. */
  133. #if __cplusplus < 201300
  134. #define return_ensures( expr, ... ) \
  135. [ & ]( ) { \
  136. decltype((expr)) _ = expr; \
  137. contract::_contract_impl<EXCEPT_T(__VA_ARGS__, std::runtime_error)>( \
  138. FIRST(__VA_ARGS__), \
  139. EXCEPT_MSG(__VA_ARGS__, \
  140. SUB_MSG("postcondition", FIRST(__VA_ARGS__), expr))); \
  141. return _; \
  142. }( )
  143. #else
  144. #define return_ensures( expr, cond, ... ) \
  145. [ _ = expr ]( ) { \
  146. contract::_contract_impl<EXCEPT_T(-, ##__VA_ARGS__, std::runtime_error)>( \
  147. cond, \
  148. EXCEPT_MSG(cond, ##__VA_ARGS__, \
  149. SUB_MSG("postcondition", cond, expr))); \
  150. return _; \
  151. }( )
  152. #endif