Created by Wojciech Migda
Kraków, 2013-10-04
the great constructor and designer Trurl was resting in his home when a sudden, loud knock on his door destroyed his peace. With grunts and curses muffled with each step, he approached the door. When he opened it in front of him stood a messenger:
‐ Sir Trurl, my King has an order for you...Trurl smiled.
The messenger introduced the problem to be solved: there are two distant alied kingdoms which have devised a communication using exchange of comets consisting of bits of information. They already know how to build them and send them into space, but so far they have failed when it actually came to extracting the information from bodies of the comets in a fast and reliable manner.
‐ Your task, Sir, will be to build two robots which will intercept each incoming comet and extract the bits of data carried inside, which then will be translated into a message.
‐ The robots must have means of controlling them programmaticaly in C++. Specificaly, it must be possible to query how many bits of data were extracted from the comet and how much space (in bytes) is needed to store the received message.
‐ It is a deal, my friend. Tell your King that the robots will be ready in a week.answered Trurl. The messenger bowed, hopped on his vehicle and swooshed away.
The robots were quite simple to build, and their infterface included these two methods:
/* get number of bits in the message */
int getSize(void) const;
/* get number of bytes of space to hold the message */
int getLength(void) const;
The messenger returned on a set date a week later. He brought the payment and left with the robots along with a nicely printed manual on how to program them.
Unfortunately, the very next day the messenger returned with a complaint.
The contractor hired by the King and appointed to write scripts to control the robots, despite having the manual, at one point made a mistake and used getLength
instead of getSize
.
He was in a hurry and the error was not caught during the code review because the names of the methods were not distinctive enough to avoid ambiguity.
Trurl agreed to rework the interface and promised that the costs will be covered by the warranty.
Learning from his mistakes he modified the interface renaming the two methods:
/* get number of bits */
int getSizeInBits(void) const;
/* get number of bytes */
int getSizeInBytes(void) const;
A month later the Messenger arrived again.
This time he brought a letter from the King with words of gratitude for the delivered robots.
The letter also mentioned that the newly established communication
between kingdoms has brought wealth and prosperity.
Lastly, the King would like to upgrade the data storage system to use words instead of bytes, and that will necesitate modification of the interface. However, the other kingdom does not plan such storage upgrade so the solution must be backward compatible.
Trurl agreed to provide the extension. He added another method:
/* get number of bits */
int getSizeInBits(void) const;
/* get number of bytes */
int getSizeInBytes(void) const;
/* get number of words */
int getSizeInWords(void) const;
When he handed over the upgrade package to the Messenger he included a hefty bill, as this time the extra work was not
covered by the warranty.
After another two weeks the messenger arrived again. This time he had two new requests.
As the messenger left Trurl began to ponder. Despite his intense efforts he could not come up with a solution which would satisfy the King's request.
Finally, he broke in tears and lamented loudly.
Trurl's cries were heard by his long time friend Klapaucius, who has just returned from the intergalactic C++11 congress and was passing by a Trurl's house.
Trurl presented his story to Klapaucius, who in turn responded with a rant on custom measurement units,
user-defined literals,
constexpr
keyword, and operator""()
.
When Trurl woke up the next day he sat down and while still remembering his friend's advice he sketched the ultimate solution.
This solution boils down to having just one method, which will return a result being of a new custom type representing general measure of information.
/* get amount of information */
Information getSize(void) const;
It will be possible to construct Information
:
empty:
Information info; // zero information
copy constructed from other instance:
Information old; // source
Information current(old);
It will only be possible to assign Information
with other instance:
Information rhs; // source
Information lhs; // destination
lhs = rhs;
It will be possible to compare Information
:
Information lhs, rhs;
lhs == rhs;
lhs != rhs;
lhs < rhs;
lhs <= rhs;
lhs > rhs;
lhs >= rhs;
It will be possible to perform some calculations on Information
.
Information lhs, rhs;
lhs += rhs;
lhs -= rhs;
lhs + rhs;
lhs - rhs;
/* these don't make sense */
lhs++;
++lhs;
lhs--;
--lhs;
Information lhs, rhs;
Information::value_t scalar;
lhs * scalar;
scalar * rhs;
lhs / scalar; /* result is of type Information */
lhs / rhs; /* result is a scalar of type Information::value_t */
lhs.div(rhs); /* result is a quotient|remainder pair
* represented by Information::pair_t */
Information
will be specified in units. Units will be explicitly stated
when a value of Information
is declared.
Information info;
info = 27_bits;
info = 14_nibbles;
info = 47_bytes;
info = 3_octets;
info = 9_words;
info = 0_dwords;
info = 24_qwords;
There will be predefined constant values of units of measurements.
Information bit; /* 1 bit*/
Information nibble; /* 1 nibble */
Information byte; /* 1 byte */
Information octet; /* 1 octet */
Information word; /* 1 word */
Information dword; /* 1 dword */
Information qword; /* 1 qword */
qword == 2 * dword == 4 * word == 8 * octet;
byte == CHAR_BIT * bit;
octet == 2 * nibble == 8 * bit;
No details of Trurl's implementation have spilled to the public, but the popular story is that the King was truely delighted with the final solution and rewarded the constructor accordingly.
But the story doesn't end here. With a bit of an effort we can follow Trurl's steps and recreate his work.
First, let's define some types:
class Information
{
public:
/* core storage type */
typedef std::size_t value_t;
};
First, let's define some types:
class Information
{
public:
typedef std::size_t value_t;
/* pair_t represents result of div */
typedef struct pair
{
value_t q; // quotient
value_t r; // remainder
constexpr pair(const value_t q, const value_t r) : q(q), r(r) {}
} pair_t;
};
Now we can declare member data:
class Information
{
private:
/* amount of information, can use arbitrary units */
value_t m_amount;
};
We will follow with construction:
class Information
{
public:
constexpr Information() : m_amount(0){}
constexpr Information(const Information & rhs) : m_amount(rhs.m_amount) {}
private:
constexpr Information(const value_t amount) : m_amount(amount) {}
};
So what does this constexpr
mean?
In short:
if arguments to the expression declared as constexpr
are constants or other
constexpr
expressions, then the value of such expression will be evaluated during
compile time.
Note: body of a constexpr
function must comprise of a single statement.
then operators:
class Information
{
public:
Information & operator+=(const Information &rhs)
{
m_amount += rhs.m_amount;
return *this;
}
Information & operator-=(const Information &rhs)
{
m_amount -= rhs.m_amount;
return *this;
}
};
and more operators:
class Information
{
public:
constexpr Information operator+(const Information & rhs) const
{return Information(m_amount + rhs.m_amount);}
constexpr Information operator-(const Information & rhs) const
{return Information(m_amount - rhs.m_amount);}
constexpr Information operator*(const value_t rhs) const
{return Information(m_amount * rhs);}
constexpr Information operator/(const value_t rhs) const
{return Information(m_amount / rhs);}
constexpr value_t operator/(const Information & rhs) const
{return m_amount / rhs.m_amount;}
constexpr value_t operator%(const Information & rhs) const
{return m_amount % rhs.m_amount;}
};
and even more operators:
class Information
{
public:
constexpr bool operator==(const Information & rhs) const
{return m_amount == rhs.m_amount;}
constexpr bool operator!=(const Information & rhs) const
{return m_amount != rhs.m_amount;}
constexpr bool operator<=(const Information & rhs) const
{return m_amount <= rhs.m_amount;}
constexpr bool operator>=(const Information & rhs) const
{return m_amount >= rhs.m_amount;}
constexpr bool operator<(const Information & rhs) const
{return m_amount < rhs.m_amount;}
constexpr bool operator>(const Information & rhs) const
{return m_amount > rhs.m_amount;}
};
basic conversions:
class Information
{
public:
constexpr value_t to(const Information & rhs) const
{return *this / rhs;}
constexpr pair_t div(const Information & rhs) const
{return pair(*this / rhs, *this % rhs);}
constexpr pair_t convert(const Information & rhs) const
{return this->div(rhs);}
};
C++11 introduced UDL syntax [over.literal]:
operator ""
identifier(parameter-declaration-clause
)
then we can write:
long operator "" _MHz(long double);
long frequency = 390.3_MHz;
We will use UDLs to define these suffixes:
constexpr Information operator"" _bit(unsigned long long x)
{return Information(x);}
constexpr Information operator"" _nibble(unsigned long long x)
{return Information(x * 4u);}
constexpr Information operator"" _byte(unsigned long long x)
{return Information(x * CHAR_BIT);}
constexpr Information operator"" _octet(unsigned long long x)
{return Information(x * 8u);}
constexpr Information operator"" _word(unsigned long long x)
{return Information(x * 16u);}
constexpr Information operator"" _dword(unsigned long long x)
{return Information(x * 32u);}
constexpr Information operator"" _qword(unsigned long long x)
{return Information(x * 64u);}
but since Information(const value_t amount)
is private we
need to friend
those operators:
class Information
{
friend constexpr Information operator"" _bit(unsigned long long x);
friend constexpr Information operator"" _nibble(unsigned long long x);
friend constexpr Information operator"" _byte(unsigned long long x);
friend constexpr Information operator"" _octet(unsigned long long x);
friend constexpr Information operator"" _word(unsigned long long x);
friend constexpr Information operator"" _dword(unsigned long long x);
friend constexpr Information operator"" _qword(unsigned long long x);
};
to finish, for convenience we will define information units:
/* in global scope */
constexpr Information bit(1_bit);
constexpr Information nibble(1_nibble);
constexpr Information byte(1_byte);
constexpr Information octet(1_octet);
constexpr Information word(1_word);
constexpr Information dword(1_dword);
constexpr Information qword(1_qword);
What difference does it make?
As an example let's take this definition:
// file main.cpp
Information foo = 147_bit;
and look at the generated assembly x86 code...
without constexpr
we will get (default optimization):
foo:
.zero 4 ;;; declaration in memory
...
movl $foo, %eax ;;; at program start
movl $147, 4(%esp)
movl $0, 8(%esp)
movl %eax, (%esp)
call _Zli4_bity ;;; ctor called
whereas having constexpr
gives:
foo:
.long 147 ;;; declaration in memory
and with an -O3
optimization:
foo:
.zero 4
...
movl $147, foo ;;; runtime initialization
constexpr
version:
foo:
.long 147
What about const
definition?
// file main.cpp
Information foo = 147_bit;
const Information goo = 279_bit;
...
return foo + goo;
regular code with -O3
optimization:
.local _ZL3goo ;;; again, declaration in memory
.comm _ZL3goo,4,4
...
movl $279, _ZL3goo ;;; and runtime initialization
...
movl _ZL3goo, %edx ;;; foo + goo
addl foo, %edx ;;; referencing memory location
constexpr
version:
movl foo, %edx ;;; foo + goo
addl $279, %edx ;;; goo is just a numeric literal