-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ARROW-6781: [C++] Improve and consolidate CHECK macros #8735
Conversation
a169b65
to
ab3a157
Compare
This PR needs rebasing now. |
1dd33a4
to
9434bf1
Compare
9434bf1
to
ecb31b2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple oddities here.
@@ -4560,7 +4560,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, | |||
CheckCommaSpacing(filename, clean_lines, linenum, error) | |||
CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) | |||
CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) | |||
CheckCheck(filename, clean_lines, linenum, error) | |||
#CheckCheck(filename, clean_lines, linenum, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason this is commented out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is a lint check which fails for DCHECK(x == y)
(consider using DCHECK_EQ(x, y)
)
#define DCHECK_LE(val1, val2) ARROW_UNUSED(val1 <= val2) | ||
#define DCHECK_LT(val1, val2) ARROW_UNUSED(val1 < val2) | ||
#define DCHECK_GE(val1, val2) ARROW_UNUSED(val1 >= val2) | ||
#define DCHECK_GT(val1, val2) ARROW_UNUSED(val1 > val2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... if the comparisons are not trivial (for example they call a function that the compiler can't see whether it has side effects), then not all code will be eliminated?
Perahsp instead something like ARROW_UNUSED(val1), ARROW_UNUSED(val2)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, but since these are debug checks anyway the giving them side effects results in differing behavior between release and debug. In general it's buggy to have non trivial code inside a debug check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not saying the comparison would have a side-effect, just that the compiler wouldn't know about it and therefore couldn't entirely eliminate the comparison code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. In that case I should prefix if (false)
so the entire expr can be dropped
// If the status is bad, CHECK immediately, appending the status to the logged | ||
// message. | ||
#define ARROW_CHECK_OK(...) \ | ||
for (::arrow::Status _s = ::arrow::internal::GenericToStatus((__VA_ARGS__)); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason for using a for loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it allows us to confine _s
to the scope of the loop body. The loop body is a single statement with no braces, allowing *CHECK_OK()
to accept a stream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but I mean compared to a regular while
or do...while
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last tokens of a do... while
loop are the loop condition, which precludes providing a stream for extra error messages.
for
supports a scoped decl, which while
doesn't. If we wanted to avoid for
, we could introduce a variable as in ARROW_ASSIGN_OR_RAISE
. After some macro expansion that'd look like:
Status _s_56 = Thing();
if (ARROW_PREDICT_FALSE(!_s_56.ok()))
ARROW_LOG(FATAL) << _s.message() << "Extra messages";
It seems more desirable to keep things scoped with a for
namespace detail { | ||
template <typename Lhs, typename Rhs> | ||
struct BoundLhsRhs { | ||
explicit constexpr operator bool() const { return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These bound expressions must be contextually convertible to bool to enable DCHECK(x >= 0 && x < 8)
. (It can't be destructured for better printing, but it should still be a valid assertion.) If the conversion to bool is not provided, then bound_expr && x < 8
breaks compilation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be a less cryptic way to implement the whole thing?
template <typename Rhs> | ||
BoundLhsRhs<Lhs, Rhs> operator<=(const Rhs& rhs) && { | ||
return {lhs, rhs}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why all these operators? They don't look like they perform the advertised operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assert condition is tested without decoration. This avoids some compiler warnings and suspicion that destructuring might slow down the fast path (the check passes).
Destructuring operates on another copy of the condition expression for printing purposes only.
It relies on the slightly higher precedence of <=
compared to the other comparison operators. After macro expansion, the intercepted comparison is as follows:
InterceptComparison() <= x > y
((InterceptComparison() <= x) > y) // parentheses for clarity
The inner parenthesis captures x
, then the outer parenthesis captures y
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question then :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the approach used by the Catch2 test framework's assertion macro. I'm not aware of another way to destructure a comparison's operands
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but destructuring in the first place shouldn't be required. I guess you want DCHECK(a == b)
to function exactly like DCHECK_EQ(a, b)
, but that doesn't sound required to me.
Otherwise, please at least explain all this in comments, because it's extremely cryptic at first sight.
}; | ||
|
||
template <typename Lhs> | ||
BoundLhs<Lhs> operator<=(const Lhs& lhs) && { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here... Don't you want to used regular method names?
closing for now |
Adds more descriptive failures for comparison DCHECKs when
ARROW_EXTRA_ERROR_CONTEXT
is defined.Note that the above destructuring evaluates operands twice. This is fine for debug checks which must be constant anyway to ensure behavior does not differ between release/debug. However
ARROW_CHECK
might be side-effectful (for example:ARROW_CHECK(PokeServer(url).successful())
). Thus if the extra context is desired there as well we'd first need to forbid side-effectfulARROW_CHECK
s; not sure it's worth the trouble.At the same time we could simply remove
DCHECK_(EQ|NE|LE|LT|GE|GT)
: IMHO they add no value. In any case that cleanup is very noisy and can wait until the smaller core of this patch is reviewed.