debug: simplify CHECK macros for constexpr safety

This commit is contained in:
Danny Robson 2016-02-03 12:04:47 +11:00
parent 9248c2f379
commit 2224131955
3 changed files with 272 additions and 242 deletions

264
debug.hpp
View File

@ -23,6 +23,7 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#ifdef ENABLE_DEBUGGING #ifdef ENABLE_DEBUGGING
#define DEBUG_ONLY(X) do { \ #define DEBUG_ONLY(X) do { \
@ -57,43 +58,34 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define _CHECK_META(C, SUCCESS, FAILURE) do { \ #define _CHECK_PANIC(FMT,...) do { \
const auto __DEBUG_value = (C); \ panic ("%s:%s:%i:%s\n" FMT, \
if (!__DEBUG_value) { \ PACKAGE, __FILE__, __LINE__, __FUNCTION__, \
std::cerr << PACKAGE << ": " \ __VA_ARGS__); \
<< __FILE__ << ":" \ } while(0)
<< __LINE__ << ": " \
<< __FUNCTION__ \
<< ". Assertion '" << #C \
<< "' failed: " << __DEBUG_value << std::endl; \
\
{ FAILURE } \
} else { \
{ SUCCESS } \
} \
} while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define CHECK(C) do { DEBUG_ONLY(_CHECK_META((C), { ; }, { panic (); });); } while (0) #define CHECK(C) do { \
DEBUG_ONLY( \
if (!(C)) \
panic (); \
); \
} while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define CHECK_EQ(A,B) do { \ #define CHECK_EQ(A,B) do { \
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
const auto __b = (B); \ const auto __b = (B); \
_CHECK_META (util::almost_equal (__a, __b), \ \
{ ; }, \ if (!util::almost_equal (__a, __b)) { \
{ \ _CHECK_PANIC("expected equality\n" \
std::ostringstream __debug_os; \ "__a: %s is %!\n" \
__debug_os.precision (15); \ "__b: %s is %!\n", \
__debug_os \ #A, __a, \
<< "expected equality.\n" \ #B, __b); \
<< "__a: " << #A << " is " << __a << ")" \ } \
<< "\n != \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
@ -103,17 +95,14 @@
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
const auto __b = (B); \ const auto __b = (B); \
_CHECK_META (__a < __b, \ \
{ ; }, \ if (__a >= __b) { \
{ \ _CHECK_PANIC("expected less than\n" \
std::ostringstream __debug_check_lt_os; \ "__a: %s is %!\n" \
__debug_check_lt_os \ "__b: %s is %!\n", \
<< "expected less than.\n" \ #A, __a, \
<< "__a: " << #A << " is " << __a << ")" \ #B, __b); \
<< "\n >= \n" \ }; \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_lt_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
@ -123,17 +112,14 @@
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
const auto __b = (B); \ const auto __b = (B); \
_CHECK_META (__a <= __b, \ \
{ ; }, \ if (__a > __b) { \
{ \ _CHECK_PANIC("expected less than or equal\n" \
std::ostringstream __debug_check_lt_os; \ "__a: %s is %!\n" \
__debug_check_lt_os \ "__b: %s is %!\n", \
<< "expected less or equal to\n" \ #A, __a, \
<< "__a: " << #A << " is " << __a << ")" \ #B, __b); \
<< "\n > \n" \ } \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_lt_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
@ -143,17 +129,14 @@
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
const auto __b = (B); \ const auto __b = (B); \
_CHECK_META (__a > __b, \ \
{ ; }, \ if (__a <= __b) { \
{ \ _CHECK_PANIC ("expected greater than\n" \
std::ostringstream __debug_check_gt_os; \ "__a: %s is %!\n" \
__debug_check_gt_os \ "__b: %s is %!\n", \
<< "expected greater than.\n" \ #A, __a, \
<< "__a: " << #A << " is " << __a << ")" \ #B, __b); \
<< "\n <= \n" \ } \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_gt_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
@ -163,39 +146,35 @@
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
const auto __b = (B); \ const auto __b = (B); \
_CHECK_META (__a >= __b, \ \
{ ; }, \ if (__a < __b) { \
{ \ _CHECK_PANIC ("expected greater or equal\n" \
std::ostringstream __debug_check_gt_os; \ "__a: %s is %!\n" \
__debug_check_gt_os \ "__b: %s is %!\n", \
<< "expected greater or equal to.\n" \ #A, __a, \
<< "__a: " << #A << " is " << __a << ")" \ #B, __b); \
<< "\n < \n" \ }; \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_gt_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define CHECK_LIMIT(VAL,LO,HI) do { \ #define CHECK_LIMIT(V,L,H) do { \
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __val = (VAL); \ const auto __v = (V); \
const auto __hi = (HI); \ const auto __l = (L); \
const auto __lo = (LO); \ const auto __h = (H); \
\ \
_CHECK_META (__val >= __lo && __val <= __hi, \ if (__v < __l || __v > __h) { \
{ ; }, \ _CHECK_PANIC ("expected limit\n" \
{ \ "__l: %s is %!\n" \
std::ostringstream __os; \ "__h: %s is %!\n" \
__os << "expected satisifies limit\n" \ "__v: %s is %!\n", \
<< "__val: " << #VAL << " = " << __val << '\n' \ #H, __h, \
<< "__lo: " << #LO << " = " << __lo << '\n' \ #L, __l, \
<< "__hi: " << #HI << " = " << __hi << '\n'; \ #V, __v); \
panic (__os.str ()); \ }; \
}); \ ); \
); \
} while (0) } while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -203,16 +182,14 @@
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
const auto __b = (B); \ const auto __b = (B); \
_CHECK_META (!util::almost_equal (__a, __b), \ \
{ ; }, \ if (util::almost_equal (__a, __b)) { \
{ \ _CHECK_PANIC ("expected inequality\n" \
std::ostringstream __debug_neq_os; \ "__a: %s is %s\n" \
__debug_neq_os << "unexpected equality.\n" \ "__b: %s is %s\n", \
<< "__a: " << #A << " is " << __a << ")" \ #A, __a, \
<< "\n == \n" \ #B, __b); \
<< "__b: " << #B << " is " << __b << ")"; \ }; \
panic (__debug_neq_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
@ -221,14 +198,12 @@
#define CHECK_ZERO(A) do { \ #define CHECK_ZERO(A) do { \
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
_CHECK_META (util::almost_zero (__a), \ \
{ ; }, \ if (!util::almost_zero (__a)) { \
{ \ _CHECK_PANIC ("expected zero\n" \
std::ostringstream __debug_nez_os; \ "__a: %s is %s\n" \
__debug_nez_os << "expected zero.\n" \ #A, __a); \
<< "__a: " << #A << " is " << __a << ")"; \ }; \
panic (__debug_nez_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
@ -237,63 +212,53 @@
#define CHECK_NEZ(A) do { \ #define CHECK_NEZ(A) do { \
DEBUG_ONLY( \ DEBUG_ONLY( \
const auto __a = (A); \ const auto __a = (A); \
_CHECK_META (!util::almost_zero (__a), \ if (util::exactly_zero (__a)) \
{ ; }, \ _CHECK_PANIC ("expected zero\n" \
{ \ "__a: %s is %!", \
std::ostringstream __debug_nez_os; \ #A, __a); \
__debug_nez_os << "unexpected zero.\n" \
<< "__a: " << #A << " is " << __a << ")"; \
panic (__debug_nez_os.str ()); \
}); \
); \ ); \
} while (0) } while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define CHECK_THROWS(E,C) do { \ #define CHECK_THROWS(E,C) do { \
DEBUG_ONLY( \ DEBUG_ONLY( \
bool caught = false; \ bool caught = false; \
\ \
try \ try \
{ C; } \ { C; } \
catch (E) \ catch (E) \
{ caught = true; } \ { caught = true; } \
\ \
if (!caught) \ if (!caught) \
panic ("expected exception: " #E); \ _CHECK_PANIC ("expected exception: %s", #E); \
); \ ); \
} while (0) } while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define CHECK_NOTHROW(C) do { \ #define CHECK_NOTHROW(C) do { \
DEBUG_ONLY( \ DEBUG_ONLY( \
try { \ try { \
C; \ C; \
} catch (...) { \ } catch (const std::exception &e) { \
panic ("unexpected exception"); \ _CHECK_PANIC ("unexpected exception: %s", \
} \ e.what ()); \
); \ } catch (...) { \
_CHECK_PANIC ("unexpected exception: %s", \
"unknown"); \
} \
); \
} while (0) } while (0)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
class panic_error {
protected:
std::string m_what;
public:
explicit panic_error (const std::string &_what):
m_what (_what)
{ ; }
};
///////////////////////////////////////////////////////////////////////////////
void panic [[noreturn]] (const std::string&);
constexpr void panic [[noreturn]] (const char*); constexpr void panic [[noreturn]] (const char*);
constexpr void panic [[noreturn]] (void); constexpr void panic [[noreturn]] (void);
template <typename ...Args>
constexpr void panic [[noreturn]] (const char *fmt, const Args&...);
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
constexpr void not_implemented [[noreturn]] (void); constexpr void not_implemented [[noreturn]] (void);
@ -368,12 +333,13 @@ namespace util { namespace debug {
} } } }
#include "./debug.ipp"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// XXX: maths needs to be included so that CHECK_EQ/NEQ can call almost_equal, // XXX: maths needs to be included so that CHECK_EQ/NEQ can call almost_equal,
// but maths.hpp might be using CHECK_ macros so we must include maths.hpp // but maths.hpp might be using CHECK_ macros so we must include maths.hpp
// after we define the CHECK_ macros so the preprocessor can resolve them. // after we define the CHECK_ macros so the preprocessor can resolve them.
#include "./maths.hpp" #include "./maths.hpp"
#include "./debug.ipp"
#endif // __DEBUG_HPP #endif // __DEBUG_HPP

View File

@ -20,11 +20,18 @@
#define __UTIL_DEBUG_IPP #define __UTIL_DEBUG_IPP
#include "./format.hpp"
#include <limits> #include <limits>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
namespace util { namespace debug { namespace detail { namespace util { namespace debug { namespace detail {
void panic [[noreturn]] (const char *msg); void panic [[noreturn]] (const char *msg);
template <typename ...Args>
void panic [[noreturn]] (const char *msg, const Args& ...args)
{ panic (util::format::render (msg, args...).c_str ()); }
void not_implemented [[noreturn]] (const char *msg); void not_implemented [[noreturn]] (const char *msg);
void unreachable [[noreturn]] (const char *msg); void unreachable [[noreturn]] (const char *msg);
} } } } } }
@ -76,13 +83,6 @@ constexpr void unreachable [[noreturn]] (const char *msg)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
inline void panic [[noreturn]] (const std::string &msg)
{
util::debug::detail::panic (msg.c_str ());
}
//-----------------------------------------------------------------------------
constexpr void panic [[noreturn]] (void) constexpr void panic [[noreturn]] (void)
{ {
panic ("nfi"); panic ("nfi");
@ -96,3 +96,15 @@ constexpr void panic [[noreturn]] (const char *msg)
? panic (msg) ? panic (msg)
: util::debug::detail::panic (msg); : util::debug::detail::panic (msg);
} }
//-----------------------------------------------------------------------------
template <typename ...Args>
constexpr
void
panic [[noreturn]] (const char *fmt, const Args& ...args)
{
! fmt
? panic ()
: util::debug::detail::panic (fmt, args...);
}

224
maths.hpp
View File

@ -38,6 +38,135 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
namespace util { namespace util {
///////////////////////////////////////////////////////////////////////////
// Comparisons
inline bool
almost_equal (const float &a, const float &b)
{
return ieee_single::almost_equal (a, b);
}
//-----------------------------------------------------------------------------
inline bool
almost_equal (const double &a, const double &b)
{
return ieee_double::almost_equal (a, b);
}
//-----------------------------------------------------------------------------
template <typename A, typename B>
typename std::enable_if_t<
std::is_floating_point<A>::value &&
std::is_floating_point<B>::value,
bool
>
almost_equal (const A &a, const B &b)
{
using common_t = std::common_type_t<A,B>;
return almost_equal<common_t> (static_cast<common_t> (a),
static_cast<common_t> (b));
}
//-----------------------------------------------------------------------------
template <typename A, typename B>
typename std::enable_if_t<
std::is_integral<A>::value &&
std::is_integral<B>::value &&
std::is_signed<A>::value == std::is_signed<B>::value,
bool
>
almost_equal (const A &a, const B &b) {
using common_t = std::common_type_t<A,B>;
return static_cast<common_t> (a) == static_cast<common_t> (b);
}
//-----------------------------------------------------------------------------
template <typename Ta, typename Tb>
typename std::enable_if<
!std::is_arithmetic<Ta>::value ||
!std::is_arithmetic<Tb>::value,
bool
>::type
almost_equal (const Ta &a, const Tb &b)
{ return a == b; }
//-----------------------------------------------------------------------------
// Useful for explictly ignore equality warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
template <typename Ta, typename Tb>
constexpr
typename std::enable_if_t<
std::is_arithmetic<Ta>::value &&
std::is_arithmetic<Tb>::value,
bool
>
exactly_equal (const Ta a, const Tb b)
{
return a == b;
}
//-------------------------------------------------------------------------
template <typename Ta, typename Tb>
typename std::enable_if_t<
!std::is_arithmetic<Ta>::value ||
!std::is_arithmetic<Tb>::value,
bool
>
exactly_equal (const Ta &a, const Tb &b)
{
return a == b;
}
#pragma GCC diagnostic pop
//-----------------------------------------------------------------------------
template <typename T>
constexpr
std::enable_if_t<
std::is_integral<T>::value, bool
>
almost_zero (T t)
{
return t == 0;
}
template <typename T>
std::enable_if_t<
!std::is_integral<T>::value, bool
>
almost_zero (T a)
{ return almost_equal (a, T{0}); }
//-------------------------------------------------------------------------
template <typename T>
constexpr
typename std::enable_if_t<
std::is_integral<T>::value, bool
>
exactly_zero (T t)
{
return exactly_equal (t, T{0});
}
template <typename T>
typename std::enable_if_t<
!std::is_integral<T>::value, bool
>
exactly_zero (T t)
{
return exactly_equal (t, T{0});
}
///////////////////////////////////////////////////////////////////////////
template <typename T> template <typename T>
T T
abs [[gnu::const]] (T t) abs [[gnu::const]] (T t)
@ -170,97 +299,20 @@ namespace util {
constexpr T constexpr T
gcd (T a, T b) gcd (T a, T b)
{ {
if (a == b) return a; CHECK_NEZ (a);
CHECK_NEZ (b);
if (a > b) return gcd (a - b, b); while (a != b) {
if (b > a) return gcd (a, b - a); if (a > b)
a -= b;
else if (b > a)
b -= a;
}
unreachable (); return a;
} }
///////////////////////////////////////////////////////////////////////////////
// Comparisons
inline bool
almost_equal (const float &a, const float &b)
{
return ieee_single::almost_equal (a, b);
}
//-----------------------------------------------------------------------------
inline bool
almost_equal (const double &a, const double &b)
{
return ieee_double::almost_equal (a, b);
}
//-----------------------------------------------------------------------------
template <typename A, typename B>
typename std::enable_if_t<
std::is_floating_point<A>::value &&
std::is_floating_point<B>::value,
bool
>
almost_equal (const A &a, const B &b)
{
using common_t = std::common_type_t<A,B>;
return almost_equal<common_t> (static_cast<common_t> (a),
static_cast<common_t> (b));
}
//-----------------------------------------------------------------------------
template <typename A, typename B>
typename std::enable_if_t<
std::is_integral<A>::value &&
std::is_integral<B>::value &&
std::is_signed<A>::value == std::is_signed<B>::value,
bool
>
almost_equal (const A &a, const B &b) {
using common_t = std::common_type_t<A,B>;
return static_cast<common_t> (a) == static_cast<common_t> (b);
}
//-----------------------------------------------------------------------------
template <typename Ta, typename Tb>
typename std::enable_if<
!std::is_arithmetic<Ta>::value ||
!std::is_arithmetic<Tb>::value,
bool
>::type
almost_equal (const Ta &a, const Tb &b)
{ return a == b; }
//-----------------------------------------------------------------------------
// Useful for explictly ignore equality warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
template <typename T, typename U>
bool
exactly_equal (const T &a, const U &b)
{ return a == b; }
#pragma GCC diagnostic pop
//-----------------------------------------------------------------------------
template <typename T>
bool
almost_zero (T a)
{ return almost_equal (a, T{0}); }
//-----------------------------------------------------------------------------
template <typename T>
bool
exactly_zero (T a)
{ return exactly_equal (a, T{0}); }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
template <typename T> template <typename T>
const T& const T&