>>> [...how a class with const members might be written...]
> programming books [...].
Delegating constructors fit nicely into a functional/recursive
approach, but they have an important limitation in that they must
be unconditional. That means we must have a helper function in
some cases to calculate values for the member variables. Here
the code is taking advantage of the property that once the number
of leading ones is known, it's easy to mask off the leading ones
from the original parameter value, using a simple expression.
That leaves the recursive function high_ones(). I think it's
pretty easy to read functions like this once you get the hang of
it. Case in point: if we see this
return bits & mask ? high_ones( n+1, bits, mask>>1 ) : n;
we might think of it as a loop
while ( bits & mask ) {
n += 1, mask >>= 1;
}
return n;
which may help bridge the gap until the recursive writing becomes
more natural.
One advantage of adopting a functional/recursive approach is that
it provides a way of "looping" in functions but which still can
be 'constexpr'.
> But the main thing that makes me unsure about this code is not the
> general approach (very nice, modulo one's familiarity with idioms of
> functional programming like in Lisp), it's the precedence of the C++
> `^`, `<<` and infix `-` operators. I'd had to look that up to be
> sure!
Here's an easy way to remember (for operators that are also in C,
which holds in this case). Binary operators all group in ways
that are natural and sensible given what they do, except for the
bitwise operators. Bitwise operators form a "sandwich" around
the relational/equality operators: shift operators have higher
precedence, and bitwise-logic operators have lower precedence.
The two kinds of shift have the same precedence; the three kinds
of bitwise-logic operations each have their own level: & and |
parallel && and ||, and ^ is inbetween. The logic operations
having lower precedence than relational/equality operators is a
historical artefact of early C not having && and ||, so it made
sense (then, not now) for & and | to be "lower" than ==, etc,
for boolean combination of conditions.
Here is a variation on the previous class definition - the helper
function calculates both member values at once, packing them into
a single 'unsigned long long' value for subsequent extraction.
Notice the private constructor needs to be 'explicit' in this
case, to avoid an overloading ambiguity.
typedef unsigned char Byte;
struct EncodedByte {
int const n_leading_ones;
Byte const low_bits;
constexpr
EncodedByte( char c )
: EncodedByte( two_parts( 0, c & 255u, 0x80 ) )
{}
private:
typedef unsigned Bits;
typedef unsigned long long ULL;
explicit constexpr
EncodedByte( ULL upper_lower )
: n_leading_ones( upper_lower >> 8 )
, low_bits( upper_lower & 255 )
{}
static constexpr ULL
two_parts( ULL n, Bits b, Bits m ){
return b & m ? two_parts( n+1, b^m, m>>1 ) : n<<8 | b;
}
};