Skip to content

Instantly share code, notes, and snippets.

@sdebionne
Created June 15, 2021 08:25
Show Gist options
  • Save sdebionne/af4e442eebef11638f2808b930b8508f to your computer and use it in GitHub Desktop.
Save sdebionne/af4e442eebef11638f2808b930b8508f to your computer and use it in GitHub Desktop.
Boost.Describe - Printing complexe data structure with annotations
// Copyright(C) 2021 Samuel Debionne, ESRF.
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0.(See accompanying file LICENSE_1_0.txt or copy at
// http: //www.boost.org/LICENSE_1_0.txt)
#include <type_traits>
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/callable_traits/class_of.hpp>
#include <boost/callable_traits/return_type.hpp>
namespace boost
{
namespace describe
{
// descriptor_by_pointer<>
namespace detail
{
template <auto pointer>
struct by_pointer
{
template <typename D, typename T1, typename T2>
struct equal { static constexpr bool value = false; };
template <typename D, typename T>
struct equal<D, T, T> { static constexpr bool value = (pointer == D::pointer); };
template <typename D>
struct fn
{
static constexpr bool value =
equal<D, std::decay_t<decltype(pointer)>, std::decay_t<decltype(D::pointer)>>::value;
};
};
} //namespace detail
template <typename Md, auto pointer, typename P = detail::by_pointer<pointer>>
using descriptor_by_pointer = mp11::mp_at<Md, mp11::mp_find_if_q<Md, P>>;
// is_described<>
template <typename T, typename Enable = void>
struct is_described : std::false_type {};
template <typename T>
struct is_described<T, std::void_t<describe_members<T, mod_any_access>>> : std::true_type {};
template <class T>
inline constexpr bool is_described_v = is_described<T>::value;
} //namespace describe
} //namespace boost
template <auto pointer>
using descriptor_by_pointer = boost::describe::descriptor_by_pointer<
boost::describe::describe_members<boost::callable_traits::class_of_t<decltype(pointer)>,
boost::describe::mod_any_access>,
pointer>;
/// Annotations of struct members
///
/// \tparam Md Member Descriptor
template <typename Md>
struct annotations;
#define APP_ANNOTATE(ptm, arg_desc, arg_doc, arg_validate) \
template <> \
struct annotations<descriptor_by_pointer<ptm>> \
{ \
inline static const char* desc = (arg_desc); \
inline static const char* doc = (arg_doc); \
\
inline static auto validate = (arg_validate); \
};
namespace app
{
template <typename T>
struct point
{
T x;
T y;
};
template <typename T>
struct rectangle
{
point<T> topleft;
point<T> dimensions;
};
BOOST_DESCRIBE_STRUCT(rectangle<std::ptrdiff_t>, (), (topleft, dimensions))
enum class acq_mode_enum : int
{
normal, //!< Single image
accumulation //!< Multiple image accumulated (over time)
};
BOOST_DESCRIBE_ENUM(acq_mode_enum, normal, accumulation)
struct acquisition
{
int nb_frames = 1; // POD
std::chrono::duration<int> expo_time = std::chrono::seconds(1); // Class
acq_mode_enum acq_mode = acq_mode_enum::normal; // Enumerator
rectangle<std::ptrdiff_t> roi; // Described class
};
BOOST_DESCRIBE_STRUCT(acquisition, (), (nb_frames, expo_time, acq_mode, roi))
} //namespace app
APP_ANNOTATE(&app::acquisition::nb_frames, "number of frames",
"The number of frames to acquire (0 = continuous acquisition)",
[](auto const& val) { return val > 0; })
APP_ANNOTATE(&app::acquisition::expo_time, "exposure time", "The exposure time [s]",
[](auto const& val) { return val > 0; })
APP_ANNOTATE(&app::acquisition::acq_mode, "acquisition mode",
"The acquistion mode [normal, accumulation]", [](auto const& val) { return true; })
APP_ANNOTATE(&app::acquisition::roi, "region of interest", "The region of interest to transfer",
[](auto const& val) { return val.validate(); })
APP_ANNOTATE(&app::rectangle<std::ptrdiff_t>::topleft, "top left corner coordinate",
"The top left corner coordinate of the region of interest to transfer",
[](auto const& val) { return val.validate(); })
APP_ANNOTATE(&app::rectangle<std::ptrdiff_t>::dimensions, "dimensions",
"The dimensions of the region of interest to transfer",
[](auto const& val) { return val.validate(); })
// Insert stream operator for std::chrono::duration
template <typename Rep>
std::ostream& operator<<(std::ostream& os, std::chrono::duration<Rep> const& duration)
{
os << duration.count() << "s";
return os;
}
// Generic insert stream operator for enumerators
template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
std::ostream& operator<<(std::ostream& os, E const& e)
{
char const* r = "(unnamed)";
boost::mp11::mp_for_each<boost::describe::describe_enumerators<E>>([&](auto D) {
if (e == D.value)
os << D.name;
});
return os;
}
// Insert stream operator for app structures
template <typename T>
std::ostream& operator<<(std::ostream& os, app::point<T> const& p)
{
os << "{" << p.x << "," << p.x << "}";
return os;
}
template <typename T>
std::ostream& operator<<(std::ostream& os, app::rectangle<T> const& r)
{
os << "{" << r.topleft.x << "," << r.topleft.y << "},"
<< "{" << r.dimensions.x << "x" << r.dimensions.y << "}";
return os;
}
// Recursively print described structures including annotations
template <class T, typename Md = boost::describe::describe_members<T, boost::describe::mod_any_access>>
void print_with_annotation(std::ostream& os, T const& t, int indent = 0)
{
bool first = true;
boost::mp11::mp_for_each<Md>([&](auto D) {
for (int i = 0 ; i < indent ; i++)
os << " ";
using A = annotations<decltype(D)>;
os << "." << D.name << " = " << t.*D.pointer << "\n\tdesc: " << A::desc << "\n\tdoc: " << A::doc << std::endl;
// Recursively print class members if they are described
using return_t = std::decay_t<boost::callable_traits::return_type_t<decltype(D.pointer)>>;
if constexpr (std::is_class_v<return_t> && boost::describe::is_described_v<return_t>)
print_with_annotation(os, t.*D.pointer, ++indent);
});
}
int main(int argc, const char* argv[])
{
using namespace std::chrono_literals;
using namespace app;
acquisition acq{100, 1s, acq_mode_enum::accumulation, {{0, 0}, {1024, 1024}}};
print_with_annotation(std::cout, acq);
return 0;
}
//$ ./gist
//.nb_frames = 100
// desc: number of frames
// doc: The number of frames to acquire (0 = continuous acquisition)
//.expo_time = 1s
// desc: exposure time
// doc: The exposure time [s]
//.acq_mode = accumulation
// desc: acquisition mode
// doc: The acquistion mode [normal, accumulation]
//.roi = {0,0},{1024x1024}
// desc: region of interest
// doc: The region of interest to transfer
// .topleft = {0,0}
// desc: top left corner coordinate
// doc: The top left corner coordinate of the region of interest to transfer
// .dimensions = {1024,1024}
// desc: dimensions
// doc: The dimensions of the region of interest to transfer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment