I have the following code:
#include <boost/hana.hpp>
#include <array>
#include <iostream>
#include <utility>
namespace hana = boost::hana;
#define HEADER_CONNECT 0b00010000
#define HEADER_CONNACK 0b00001000
struct ConnectFrame
{
uint8_t header = 16;
uint8_t variable = 2;
};
struct ConnackFrame
{
uint8_t header = 8;
uint8_t variable = 3;
};
constexpr auto FramesMap = hana::make_tuple(
hana::make_pair(hana::type_c<ConnectFrame>, hana::integral_c<std::uint8_t, HEADER_CONNECT>),
hana::make_pair(hana::type_c<ConnackFrame>, hana::integral_c<std::uint8_t, HEADER_CONNACK>));
//! Runtime deserialization switch based on FramesMap
template <typename InputIterator>
auto deserializeByFrameHeader(const std::uint8_t frameHeader, const InputIterator buffer)
{
auto found = hana::index_if(FramesMap, [&frameHeader = std::as_const(frameHeader)](auto const &pair) {
return hana::second(pair) == hana::integral_c<std::uint8_t, frameHeader>;
});
auto FrameType = hana::first(hana::at(FramesMap, found.value()));
using T = typename decltype(FrameType)::type;
T var;
//deserialize(buffer, var);
return var;
}
int main()
{
std::array<std::byte, 128> buffer;
// for dummy purposes we assume that the first byte of the buffer array after serialization is 8
const uint8_t header = 8;
ConnackFrame frameOut = deserializeByFrameHeader(header, buffer.begin());
}
I try to find the index of the pair in the tuple which matches the variable frameHeader. Unfortunately, I get a compilation error:
../include/minimalMQTT.hpp:178:43: error: 'this' is not a constant expression
178 | return hana::second(pair) == hana::integral_c<std::uint8_t, frameHeader>;
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How can the variable frameHeader
be declared as a constant expression in order to make this work?
hana::integral_c<std::uint8_t, frameHeader>
integral_c
is a type that encodes a statically known value. However you try to instantiate it with frameHeader
which is not statically known.
To map runtime-values on compile-tume values the best you can do is a mapping (sometimes using binary search). However, you can also check whether you require this compiletime evaluated.
In your specific example you can work around things by using a constexpr lambda (given a recent enough compiler!).
In practice I doubt this would suit your needs, ever, but just so you're aware of the trick:
#include <boost/hana.hpp>
#include <boost/core/ignore_unused.hpp>
#include <array>
#include <iostream>
#include <utility>
namespace hana = boost::hana;
#define HEADER_CONNECT 0b00010000
#define HEADER_CONNACK 0b00001000
struct ConnectFrame
{
uint8_t header = 16;
uint8_t variable = 2;
};
struct ConnackFrame
{
uint8_t header = 8;
uint8_t variable = 3;
};
constexpr auto FramesMap = hana::make_tuple(
hana::make_pair(hana::type_c<ConnectFrame>, hana::integral_c<std::uint8_t, HEADER_CONNECT>),
hana::make_pair(hana::type_c<ConnackFrame>, hana::integral_c<std::uint8_t, HEADER_CONNACK>));
//! Runtime deserialization switch based on FramesMap
template <typename FrameHeader, typename InputIterator>
auto deserializeByFrameHeader(FrameHeader const frameHeader, const InputIterator buffer)
{
auto found = hana::index_if(FramesMap, [=](auto const &pair) constexpr {
return hana::second(pair) == hana::integral_c<std::uint8_t, frameHeader()>;
});
auto FrameType = hana::first(hana::at(FramesMap, found.value()));
using T = typename decltype(FrameType)::type;
T var;
boost::ignore_unused(buffer);
//deserialize(buffer, var);
return var;
}
int main()
{
std::array<std::byte, 128> buffer;
// for dummy purposes we assume that the first byte of the buffer array after serialization is 8
ConnackFrame frameOut = deserializeByFrameHeader(
[]() constexpr { return 8; },
buffer.begin());
boost::ignore_unused(frameOut);
}
Technically, the header is the first byte of the buffer array, i.e. uint8_t header = (uint8_t)buffer[0]. Would it be possible to omit the header argument and extract the header from the buffer as a constexpr directly?
No.
The return type is fixed. The input is dynamic. There's no way to pave over that (usefully/efficiently).
By the way, what would be a solution if I don't need it compile time evaluated?
Since you're parsing protocol messages you're naturally switching on type ids (because that's how they exist on the wire). As a serious C++ programmer you naturally want to jump the abstraction layer to proper type-switching as soon as possible.
std::variant<...>
with visitation.Depending on your usage patterns and processing needs either might be more applicable. std::variant
has the nice feature that it encodes the type in a switchable manner, but visitation retains static type information. This means: technically you may be able to leverage static type information, inlining and all the optimization goodness.
It seems that this is what you're after. So I'd suggest:
#include <array>
#include <iostream>
#include <variant>
constexpr uint8_t HEADER_CONNECT = 0b00010000;
constexpr uint8_t HEADER_CONNACK = 0b00001000;
struct ConnectFrame {
uint8_t header = 16;
uint8_t variable = 2;
};
struct ConnackFrame {
uint8_t header = 8;
uint8_t variable = 3;
};
// Static typed land
void handler(ConnectFrame const&) { std::cout << "Handling ConnectFrame\n"; }
void handler(ConnackFrame const&) { std::cout << "Handling ConnackFrame\n"; }
template <typename InputIterator>
void deserialize(InputIterator&, ConnectFrame&) { /*TODO*/ }
template <typename InputIterator>
void deserialize(InputIterator&, ConnackFrame&) { /*TODO*/ }
template <typename Frame, typename InputIterator>
Frame deserialize(InputIterator& buffer) {
Frame frame;
deserialize(buffer, frame);
return frame;
}
// Type-swithcing land
template <typename InputIterator>
constexpr inline std::uint8_t frameHeader(InputIterator& buffer) {
return static_cast<std::uint8_t>(*buffer++);
}
using AnyFrame = std::variant<ConnectFrame, ConnackFrame>;
template <typename InputIterator>
AnyFrame deserializeByFrameHeader(InputIterator&& buffer) {
switch (uint8_t h = frameHeader(buffer)) {
case HEADER_CONNECT: return deserialize<ConnectFrame>(buffer);
case HEADER_CONNACK: return deserialize<ConnackFrame>(buffer);
}
throw std::range_error("frameHeader");
}
int main() {
constexpr auto process = [](auto const& frame) { handler(frame); };
using Buffer = std::array<std::byte, 128>;
for (auto buffer : { Buffer
{ std::byte(HEADER_CONNECT), std::byte(0x12), std::byte(0x34), },
{ std::byte(HEADER_CONNACK), std::byte(0xab), std::byte(0xcd), } })
{
auto frameOut = deserializeByFrameHeader(buffer.begin());
std::visit(process, frameOut);
}
}
Which prints
Handling ConnectFrame
Handling ConnackFrame
If you really think it's important to work from the mappings table, you can, using a bit more code and compiler sweat:
constexpr auto FramesMap = hana::make_tuple(
hana::make_pair(hana::type_c<ConnectFrame>, HEADER_CONNECT),
hana::make_pair(hana::type_c<ConnackFrame>, HEADER_CONNACK)
);
Note how I dropped the
integral_c
because we don't need it
Let's make AnyFrame
a variant over the frame-types:
constexpr auto FrameTypes = hana::transform(FramesMap, hana::first);
using AnyFrame = decltype(
hana::unpack(FrameTypes, hana::template_<std::variant>))
::type;
Now, let's reimplement deserializeByFrameHeader
using it:
template <typename InputIterator>
AnyFrame deserializeByFrameHeader(InputIterator&& buffer) {
AnyFrame retval;
hana::for_each(FramesMap,
[&, frameHeader = frameHeader(buffer)](auto const &pair) {
auto first = hana::first(pair);
using T = typename decltype(first)::type;
if (hana::second(pair) == frameHeader) {
retval.emplace<T>();
deserialize(buffer, std::get<T>(retval));
}
});
return retval;
}
Note the the simplification: we kept everything depending on the static type of the tuple element (pair
) inside the polymorphic lambda, where we have the frametype available at all times.
#include <cstdint>
constexpr uint8_t HEADER_CONNECT = 0b00010000;
constexpr uint8_t HEADER_CONNACK = 0b00001000;
struct ConnectFrame {
uint8_t header = 16;
uint8_t variable = 2;
};
struct ConnackFrame {
uint8_t header = 8;
uint8_t variable = 3;
};
#include <boost/hana.hpp>
#include <stdexcept>
#include <variant>
#include <iostream>
namespace {
namespace hana = boost::hana;
constexpr auto FramesMap = hana::make_tuple(
hana::make_pair(hana::type_c<ConnectFrame>, HEADER_CONNECT),
hana::make_pair(hana::type_c<ConnackFrame>, HEADER_CONNACK)
);
constexpr auto FrameTypes = hana::transform(FramesMap, hana::first);
using AnyFrame = decltype(
hana::unpack(FrameTypes, hana::template_<std::variant>))
::type;
}
// Static typed land
void handler(ConnectFrame const&) { std::cout << "Handling ConnectFrame\n"; }
void handler(ConnackFrame const&) { std::cout << "Handling ConnackFrame\n"; }
template <typename InputIterator>
void deserialize(InputIterator&, ConnectFrame&) { /*TODO*/ }
template <typename InputIterator>
void deserialize(InputIterator&, ConnackFrame&) { /*TODO*/ }
// Type-swithcing land
template <typename InputIterator>
constexpr inline std::uint8_t frameHeader(InputIterator& buffer) {
return static_cast<std::uint8_t>(*buffer++);
}
template <typename InputIterator>
AnyFrame deserializeByFrameHeader(InputIterator&& buffer) {
AnyFrame retval;
hana::for_each(FramesMap,
[&, frameHeader = frameHeader(buffer)](auto const &pair) {
auto first = hana::first(pair);
using T = typename decltype(first)::type;
if (hana::second(pair) == frameHeader) {
retval.emplace<T>();
deserialize(buffer, std::get<T>(retval));
}
});
return retval;
}
#include <array>
int main() {
constexpr auto process = [](auto const& frame) { handler(frame); };
using Buffer = std::array<std::byte, 128>;
for (auto buffer : { Buffer
{ std::byte(HEADER_CONNECT), std::byte(0x12), std::byte(0x34), },
{ std::byte(HEADER_CONNACK), std::byte(0xab), std::byte(0xcd), } })
{
auto frameOut = deserializeByFrameHeader(buffer.begin());
std::visit(process, frameOut);
}
}
Prints
Handling ConnectFrame
Handling ConnackFrame