...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Let's consider the following example:
Code |
---|
#define BOOST_TEST_MODULE boost_test_macro3 #include <boost/test/included/unit_test.hpp> BOOST_AUTO_TEST_CASE( test_op_reportings ) { int a = 13, b = 12; BOOST_TEST(a == b); BOOST_TEST(a < b); BOOST_TEST(a - 1 < b); BOOST_TEST(b > a - 1); } |
Output |
---|
> ./boost_test_macro3 --log_level=all Running 1 test case... Entering test module "boost_test_macro3" test.cpp(12): Entering test case "test_op_reportings" test.cpp(15): error: in "test_op_reportings": check a == b has failed [13 != 12] test.cpp(16): error: in "test_op_reportings": check a < b has failed [13 >= 12] test.cpp(17): error: in "test_op_reportings": check a - 1 < b has failed [13 - 1 >= 12] test.cpp(18): error: in "test_op_reportings": check b > a - 1 has failed [12 <= 12] test.cpp(12): Leaving test case "test_op_reportings"; testing time: 484us Leaving test module "boost_test_macro3"; testing time: 588us *** 2 failures are detected in the test module "boost_test_macro3" |
It was already mentioned that the reporting is not symmetrical (see here). An expression is
constructed from the statement
appearing in the BOOST_TEST
macro. This expression allows evaluation and reporting such as "13 - 1 >= 12" failed
along with a copy of the statement
,
which contains more details than "a - 1
< b" failed
. In
details, what happens is the following:
a special object, the seed
of the expression, is composed from the left side of statement
.
This initial composition has highest precedence over the supported operations.
The expression below:
a op1 b op2 c op3 d
is actually seen as
( seed a ) op1 b op2 c op3 d
The "seed a
"
returns an expression
object that keep tracks of the type of a
.
This expression has overloads for left-to-right associativity, and the
operations op1
, op2
... are chained
to the right of this expression object:
a op1 b
yields to the pseudo-code
expression1 = create-expression(a) expression2 = create-expression(expression1, op1, b)
expression1
and expression2
keep track of their left
and right operands, and the operation on those operands. The expressions
keep also track of the result type of the associated sub-expression.
In the above example, expression1
and expression2
have
result type decltype(a)
and decltype(a op1
b)
respectively. The result type allows for chaining sub-expressions.
The C++ operators precedence rules apply in any case. What is seen by
the expression is what is reachable with left-to-right composition. Any
other operation that happens before it reaches the expression's right
operand is not parsed as a sub-expression and is seen as a single operand:
the right operand is not developed further by the framework. Let's suppose
op2
below has higher
precedence than op1
,
then
a op1 b op2 c
is equivalent to:
create-expression(create-expression(a), op1, (b op2 c))
In the above statement, the final expression can only see the result
of (b
op2 c)
to its right, for which no further detail
can be provided in the logs. This is also the case for right-to-left
associative operators, such as !
,
~
, -
(unary negation) etc.
Caution | |
---|---|
Since the |
Once the full expression chain is built, it is evaluated as a chain of sub-expressions from left-to-right, exactly as the composition rule above. The evaluated elements are the ones of the expression itself. The expression
a op1 b
yields to the following evaluation chain:
expression2.result = expression1.result op1 b expression1.result = a
The final expression of the statement is cast to a boolean, which is in turn evaluated by the Unit Test Framework.
The example below illustrates the construction of the left-to-right chained expression.
Code |
---|
#define BOOST_TEST_MODULE boost_test_macro2 #include <boost/test/included/unit_test.hpp> BOOST_AUTO_TEST_CASE( test_op_precedence ) { int a = 13, b = 2, c = 12; // left term of == is expanded in the logs BOOST_TEST(a % b == c); // right term of == is not expanded in the logs BOOST_TEST(a == c % b); } BOOST_AUTO_TEST_CASE( test_op_right_associative ) { int a = 1; BOOST_TEST(a); BOOST_TEST(!a); BOOST_TEST(--a); } |
Output |
---|
> ./boost_test_macro2 --log_level=all Running 2 test cases... test.cpp(16): error: in "test_op_precedence": check a % b == c has failed [13 % 2 != 12] test.cpp(18): error: in "test_op_precedence": check a == c % b has failed [13 != 0] test.cpp(25): error: in "test_op_right_associative": check !a has failed test.cpp(26): error: in "test_op_right_associative": check --a has failed [(bool)0 is false] *** 4 failures are detected in the test module "boost_test_macro2" |