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 <string>
///////////////////////////////////////////////////////////////////////////////
#ifdef ENABLE_DEBUGGING
#define DEBUG_ONLY(X) do { \
@ -57,43 +58,34 @@
///////////////////////////////////////////////////////////////////////////////
#define _CHECK_META(C, SUCCESS, FAILURE) do { \
const auto __DEBUG_value = (C); \
if (!__DEBUG_value) { \
std::cerr << PACKAGE << ": " \
<< __FILE__ << ":" \
<< __LINE__ << ": " \
<< __FUNCTION__ \
<< ". Assertion '" << #C \
<< "' failed: " << __DEBUG_value << std::endl; \
\
{ FAILURE } \
} else { \
{ SUCCESS } \
} \
} while (0)
#define _CHECK_PANIC(FMT,...) do { \
panic ("%s:%s:%i:%s\n" FMT, \
PACKAGE, __FILE__, __LINE__, __FUNCTION__, \
__VA_ARGS__); \
} 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 { \
DEBUG_ONLY( \
const auto __a = (A); \
const auto __b = (B); \
_CHECK_META (util::almost_equal (__a, __b), \
{ ; }, \
{ \
std::ostringstream __debug_os; \
__debug_os.precision (15); \
__debug_os \
<< "expected equality.\n" \
<< "__a: " << #A << " is " << __a << ")" \
<< "\n != \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_os.str ()); \
}); \
\
if (!util::almost_equal (__a, __b)) { \
_CHECK_PANIC("expected equality\n" \
"__a: %s is %!\n" \
"__b: %s is %!\n", \
#A, __a, \
#B, __b); \
} \
); \
} while (0)
@ -103,17 +95,14 @@
DEBUG_ONLY( \
const auto __a = (A); \
const auto __b = (B); \
_CHECK_META (__a < __b, \
{ ; }, \
{ \
std::ostringstream __debug_check_lt_os; \
__debug_check_lt_os \
<< "expected less than.\n" \
<< "__a: " << #A << " is " << __a << ")" \
<< "\n >= \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_lt_os.str ()); \
}); \
\
if (__a >= __b) { \
_CHECK_PANIC("expected less than\n" \
"__a: %s is %!\n" \
"__b: %s is %!\n", \
#A, __a, \
#B, __b); \
}; \
); \
} while (0)
@ -123,17 +112,14 @@
DEBUG_ONLY( \
const auto __a = (A); \
const auto __b = (B); \
_CHECK_META (__a <= __b, \
{ ; }, \
{ \
std::ostringstream __debug_check_lt_os; \
__debug_check_lt_os \
<< "expected less or equal to\n" \
<< "__a: " << #A << " is " << __a << ")" \
<< "\n > \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_lt_os.str ()); \
}); \
\
if (__a > __b) { \
_CHECK_PANIC("expected less than or equal\n" \
"__a: %s is %!\n" \
"__b: %s is %!\n", \
#A, __a, \
#B, __b); \
} \
); \
} while (0)
@ -143,17 +129,14 @@
DEBUG_ONLY( \
const auto __a = (A); \
const auto __b = (B); \
_CHECK_META (__a > __b, \
{ ; }, \
{ \
std::ostringstream __debug_check_gt_os; \
__debug_check_gt_os \
<< "expected greater than.\n" \
<< "__a: " << #A << " is " << __a << ")" \
<< "\n <= \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_gt_os.str ()); \
}); \
\
if (__a <= __b) { \
_CHECK_PANIC ("expected greater than\n" \
"__a: %s is %!\n" \
"__b: %s is %!\n", \
#A, __a, \
#B, __b); \
} \
); \
} while (0)
@ -163,39 +146,35 @@
DEBUG_ONLY( \
const auto __a = (A); \
const auto __b = (B); \
_CHECK_META (__a >= __b, \
{ ; }, \
{ \
std::ostringstream __debug_check_gt_os; \
__debug_check_gt_os \
<< "expected greater or equal to.\n" \
<< "__a: " << #A << " is " << __a << ")" \
<< "\n < \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_check_gt_os.str ()); \
}); \
\
if (__a < __b) { \
_CHECK_PANIC ("expected greater or equal\n" \
"__a: %s is %!\n" \
"__b: %s is %!\n", \
#A, __a, \
#B, __b); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_LIMIT(VAL,LO,HI) do { \
DEBUG_ONLY( \
const auto __val = (VAL); \
const auto __hi = (HI); \
const auto __lo = (LO); \
\
_CHECK_META (__val >= __lo && __val <= __hi, \
{ ; }, \
{ \
std::ostringstream __os; \
__os << "expected satisifies limit\n" \
<< "__val: " << #VAL << " = " << __val << '\n' \
<< "__lo: " << #LO << " = " << __lo << '\n' \
<< "__hi: " << #HI << " = " << __hi << '\n'; \
panic (__os.str ()); \
}); \
); \
#define CHECK_LIMIT(V,L,H) do { \
DEBUG_ONLY( \
const auto __v = (V); \
const auto __l = (L); \
const auto __h = (H); \
\
if (__v < __l || __v > __h) { \
_CHECK_PANIC ("expected limit\n" \
"__l: %s is %!\n" \
"__h: %s is %!\n" \
"__v: %s is %!\n", \
#H, __h, \
#L, __l, \
#V, __v); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
@ -203,16 +182,14 @@
DEBUG_ONLY( \
const auto __a = (A); \
const auto __b = (B); \
_CHECK_META (!util::almost_equal (__a, __b), \
{ ; }, \
{ \
std::ostringstream __debug_neq_os; \
__debug_neq_os << "unexpected equality.\n" \
<< "__a: " << #A << " is " << __a << ")" \
<< "\n == \n" \
<< "__b: " << #B << " is " << __b << ")"; \
panic (__debug_neq_os.str ()); \
}); \
\
if (util::almost_equal (__a, __b)) { \
_CHECK_PANIC ("expected inequality\n" \
"__a: %s is %s\n" \
"__b: %s is %s\n", \
#A, __a, \
#B, __b); \
}; \
); \
} while (0)
@ -221,14 +198,12 @@
#define CHECK_ZERO(A) do { \
DEBUG_ONLY( \
const auto __a = (A); \
_CHECK_META (util::almost_zero (__a), \
{ ; }, \
{ \
std::ostringstream __debug_nez_os; \
__debug_nez_os << "expected zero.\n" \
<< "__a: " << #A << " is " << __a << ")"; \
panic (__debug_nez_os.str ()); \
}); \
\
if (!util::almost_zero (__a)) { \
_CHECK_PANIC ("expected zero\n" \
"__a: %s is %s\n" \
#A, __a); \
}; \
); \
} while (0)
@ -237,63 +212,53 @@
#define CHECK_NEZ(A) do { \
DEBUG_ONLY( \
const auto __a = (A); \
_CHECK_META (!util::almost_zero (__a), \
{ ; }, \
{ \
std::ostringstream __debug_nez_os; \
__debug_nez_os << "unexpected zero.\n" \
<< "__a: " << #A << " is " << __a << ")"; \
panic (__debug_nez_os.str ()); \
}); \
if (util::exactly_zero (__a)) \
_CHECK_PANIC ("expected zero\n" \
"__a: %s is %!", \
#A, __a); \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_THROWS(E,C) do { \
DEBUG_ONLY( \
bool caught = false; \
\
try \
{ C; } \
catch (E) \
{ caught = true; } \
\
if (!caught) \
panic ("expected exception: " #E); \
); \
#define CHECK_THROWS(E,C) do { \
DEBUG_ONLY( \
bool caught = false; \
\
try \
{ C; } \
catch (E) \
{ caught = true; } \
\
if (!caught) \
_CHECK_PANIC ("expected exception: %s", #E); \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_NOTHROW(C) do { \
DEBUG_ONLY( \
try { \
C; \
} catch (...) { \
panic ("unexpected exception"); \
} \
); \
#define CHECK_NOTHROW(C) do { \
DEBUG_ONLY( \
try { \
C; \
} catch (const std::exception &e) { \
_CHECK_PANIC ("unexpected exception: %s", \
e.what ()); \
} catch (...) { \
_CHECK_PANIC ("unexpected exception: %s", \
"unknown"); \
} \
); \
} 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]] (void);
template <typename ...Args>
constexpr void panic [[noreturn]] (const char *fmt, const Args&...);
///////////////////////////////////////////////////////////////////////////////
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,
// 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.
#include "./maths.hpp"
#include "./debug.ipp"
#endif // __DEBUG_HPP

View File

@ -20,11 +20,18 @@
#define __UTIL_DEBUG_IPP
#include "./format.hpp"
#include <limits>
///////////////////////////////////////////////////////////////////////////////
namespace util { namespace debug { namespace detail {
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 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)
{
panic ("nfi");
@ -96,3 +96,15 @@ constexpr void panic [[noreturn]] (const char *msg)
? 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 {
///////////////////////////////////////////////////////////////////////////
// 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>
T
abs [[gnu::const]] (T t)
@ -170,97 +299,20 @@ namespace util {
constexpr T
gcd (T a, T b)
{
if (a == b) return a;
CHECK_NEZ (a);
CHECK_NEZ (b);
if (a > b) return gcd (a - b, b);
if (b > a) return gcd (a, b - a);
while (a != b) {
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>
const T&