The Logical
concept represents types with a truth value.
Intuitively, a Logical
is just a bool
, or something that can act like one. However, in the context of programming with heterogeneous objects, it becomes extremely important to distinguish between those objects whose truth value is known at compiletime, and those whose truth value is only known at runtime. The reason why this is so important is because it is possible to branch at compiletime on a condition whose truth value is known at compiletime, and hence the return type of the enclosing function can depend on that truth value. However, if the truth value is only known at runtime, then the compiler has to compile both branches (because any or both of them may end up being used), which creates the additional requirement that both branches must evaluate to the same type.
More specifically, Logical
(almost) represents a boolean algebra, which is a mathematical structure encoding the usual properties that allow us to reason with bool
. The exact properties that must be satisfied by any model of Logical
are rigorously stated in the laws below.
A Logical
x
is said to be truevalued, or sometimes also just true as an abuse of notation, if
Similarly, x
is falsevalued, or sometimes just false, if
This provides a standard way of converting any Logical
to a straight bool
. The notion of truth value suggests another definition, which is that of logical equivalence. We will say that two Logical
s x
and y
are logically equivalent if they have the same truth value. To denote that some expressions p
and q
of a Logical data type are logically equivalent, we will sometimes also write
which is very common in mathematics. The intuition behind this notation is that whenever p
is truevalued, then q
should be; but when p
is falsevalued, then q
should be too. Hence, p
should be truevalued when (and only when) q
is truevalued.
eval_if
, not_
and while_
All the other functions can be defined in those terms:
As outlined above, the Logical
concept almost represents a boolean algebra. The rationale for this laxity is to allow things like integers to act like Logical
s, which is aligned with C++, even though they do not form a boolean algebra. Even though we depart from the usual axiomatization of boolean algebras, we have found through experience that the definition of a Logical given here is largely compatible with intuition.
The following laws must be satisfied for any data type L
modeling the Logical
concept. Let a
, b
and c
be objects of a Logical
data type, and let t
and f
be arbitrary truevalued and falsevalued Logical
s of that data type, respectively. Then,
If you look closely, you will find that we depart from the usual boolean algebras because:
and_
and or_
operationsA data type T
is arithmetic if std::is_arithmetic<T>::value
is true. For an arithmetic data type T
, a model of Logical
is provided automatically by using the result of the builtin implicit conversion to bool
as a truth value. Specifically, the minimal complete definition for those data types is
The not_
method can not be implemented in a meaningful way for all of those types. For example, one can not cast a pointer type T*
to bool and then back again to T*
in a meaningful way. With an arithmetic type T
, however, it is possible to cast from T
to bool and then to T
again; the result will be 0
or 1
depending on the truth value. If you want to use a pointer type or something similar in a conditional, it is suggested to explicitly convert it to bool by using to<bool>
.
Variables  
constexpr auto  boost::hana::and_ 
Return whether all the arguments are truevalued. More...  
constexpr auto  boost::hana::eval_if 
Conditionally execute one of two branches based on a condition. More...  
constexpr auto  boost::hana::if_ 
Conditionally return one of two values based on a condition. More...  
constexpr auto  boost::hana::not_ 
Negates a Logical . More...  
constexpr auto  boost::hana::or_ 
Return whether any of the arguments is truevalued. More...  
constexpr auto  boost::hana::while_ 
Apply a function to an initial state while some predicate is satisfied. More...  

constexpr 
#include <boost/hana/fwd/and.hpp>
Return whether all the arguments are truevalued.
and_
can be called with one argument or more. When called with two arguments, and_
uses tagdispatching to find the right implementation. Otherwise,

constexpr 
#include <boost/hana/fwd/eval_if.hpp>
Conditionally execute one of two branches based on a condition.
Given a condition and two branches in the form of lambdas or hana::lazy
s, eval_if
will evaluate the branch selected by the condition with eval
and return the result. The exact requirements for what the branches may be are the same requirements as those for the eval
function.
By passing a unary callable to eval_if
, it is possible to defer the compiletime evaluation of selected expressions inside the lambda. This is useful when instantiating a branch would trigger a compiletime error; we only want the branch to be instantiated when that branch is selected. Here's how it can be achieved.
For simplicity, we'll use a unary lambda as our unary callable. Our lambda must accept a parameter (usually called _
), which can be used to defer the compiletime evaluation of expressions as required. For example,
What happens here is that eval_if
will call eval
on the selected branch. In turn, eval
will call the selected branch either with nothing – for the then branch – or with hana::id
– for the else branch. Hence, _(x)
is always the same as x
, but the compiler can't tell until the lambda has been called! Hence, the compiler has to wait before it instantiates the body of the lambda and no infinite recursion happens. However, this trick to delay the instantiation of the lambda's body can only be used when the condition is known at compiletime, because otherwise both branches have to be instantiated inside the eval_if
anyway.
There are several caveats to note with this approach to lazy branching. First, because we're using lambdas, it means that the function's result can't be used in a constant expression. This is a limitation of the current language.
The second caveat is that compilers currently have several bugs regarding deeply nested lambdas with captures. So you always risk crashing the compiler, but this is a question of time before it is not a problem anymore.
Finally, it means that conditionals can't be written directly inside unevaluated contexts. The reason is that a lambda can't appear in an unevaluated context, for example in decltype
. One way to workaround this is to completely lift your type computations into variable templates instead. For example, instead of writing
you could instead write
Note: This example would actually be implemented more easily with partial specializations, but my bag of good examples is empty at the time of writing this.
Now, this hoopjumping only has to be done in one place, because you should use normal function notation everywhere else in your metaprogram to perform type computations. So the syntactic cost is amortized over the whole program.
Another way to work around this limitation of the language would be to use hana::lazy
for the branches. However, this is only suitable when the branches are not too complicated. With hana::lazy
, you could write the previous example as
cond  The condition determining which of the two branches is selected. 
then  An expression called as eval(then) if cond is truevalued. 
else_  A function called as eval(else_) if cond is falsevalued. 

constexpr 
#include <boost/hana/fwd/if.hpp>
Conditionally return one of two values based on a condition.
Specifically, then
is returned iff cond
is truevalued, and else_
is returned otherwise. Note that some Logical
models may allow then
and else_
to have different types, while others may require both values to have the same type.
cond  The condition determining which of the two values is returned. 
then  The value returned when cond is truevalued. 
else_  The value returned when cond is falsevalued. 

constexpr 
#include <boost/hana/fwd/not.hpp>
Negates a Logical
.
This method returns a Logical
with the same tag, but whose truthvalue is negated. Specifically, not_(x)
returns a falsevalued Logical
if x
is a truevalued Logical
, and a truevalued one otherwise.

constexpr 
#include <boost/hana/fwd/or.hpp>
Return whether any of the arguments is truevalued.
or_
can be called with one argument or more. When called with two arguments, or_
uses tagdispatching to find the right implementation. Otherwise,

constexpr 
#include <boost/hana/fwd/while.hpp>
Apply a function to an initial state while some predicate is satisfied.
This method is a natural extension of the while
language construct to manipulate a state whose type may change from one iteration to another. However, note that having a state whose type changes from one iteration to the other is only possible as long as the predicate returns a Logical
whose truth value is known at compiletime.
Specifically, while_(pred, state, f)
is equivalent to
where f
is iterated as long as pred(f(...))
is a truevalued Logical
.
pred  A predicate called on the state or on the result of applying f a certain number of times to the state, and returning whether f should be applied one more time. 
state  The initial state on which f is applied. 
f  A function that is iterated on the initial state. Note that the return type of f may change from one iteration to the other, but only while pred returns a compiletime Logical . In other words, decltype(f(stateN)) may differ from decltype(f(stateN+1)) , but only if pred(f(stateN)) returns a compiletime Logical . 