Open sdebionne opened 3 years ago
There seems to be a significant pushback in the committee against the idea of reflection on attributes. The objection is that attributes today can be silently ignored by the compiler, and this feature is deemed extremely important for some reason. The suggestion of those who object is that these annotations need to use a keyword, rather than the attribute syntax.
I dont understand why having attributes (today) silently ignored by the compiler make them unsuitable. Why not make them eventually silently ignored but mandatorily reflected? Why another keyword? That's questions for the committee... In the meantime, external annotations and #15 will do the trick.
Hello everyone! I recently discovered an interesting way to describe custom attributes in C++17. I propose to consider it.
#ifdef _STR
# error _STR already defined
#endif
#define _STR(S) BOOST_METAPARSE_STRING(S){}
boost::describe::field<int, (units=cm), (description=_STR("this is sparta"))> x;
This short example shows that it is possible to implement it. At least - for one attribute :-) https://godbolt.org/z/ad1ojzch7
I would prefer a solution that does not require to use a wrapper type (eg field
), something less intrusive. Here is what I came up with, loosely inspired from the Describe library.
The annotations (members do not need to have the same type/number of annotations):
struct position
{
int distance;
float timestamp;
};
BOOST_DESCRIBE_STRUCT(position, (), (distance, timestamp))
BOOST_ANNOTATE_MEMBER(position, distance,
(unit, "meter"),
(doc, "The distance travelled from the start line"))
BOOST_ANNOTATE_MEMBER(position, timestamp,
(unit, "second"),
(doc, "The EPOCH timstamp since the beginning of the run"))
And the usage:
// For each member descriptors
using Md = boost::describe::describe_members<T, boost::describe::mod_any_access>
boost::mp11::mp_for_each<Md>([&](auto D) {
// Get annotation for the member descriptor
using A = boost::describe::annotate_member<T, decltype(D)>;
std::cout << "\nAnnotations:\n";
boost::mp11::mp_for_each<A>([&](auto a) {
std::cout << "." << a.name << " = " << a.value << std::endl;
});
});
https://godbolt.org/z/7PbobnMxW
@pdimov Do you think something like this is worth adding to the library?
@sdebionne Is there really a need for a separate BOOST_ANNOTATE_MEMBER
? Wouldn't it be better to package it all in one BOOST_DESCRIBE_STRUCT
? Like this:
struct position
{
int distance;
float timestamp;
};
BOOST_DESCRIBE_STRUCT(position, (), (
(distance,
(unit, "meter"),
(doc, "The distance travelled from the start line")
),
(timestamp,
(unit, "second"),
(doc, "The EPOCH timstamp since the beginning of the run")
)
))
The rationale was mostly to keep annotation as an extension of Boost.Describe. But I like your suggestion too.
I have a project that uses Annotation to generate a Json Schema from a described and annotated data structure. Here is the latest iteration of the annotation bits:
If there is interest in this, I can move forward and make a PR with tests and doc...
If i understand you right, your solution can be used like this:
BOOST_ANNOTATE_MEMBER(position, distance,
(unit, "meter"))
BOOST_ANNOTATE_MEMBER(position, distance,
(doc, "The distance travelled from the start line"))
// member position::distance has two attributes - unit and doc
This is perfect together with my suggestion and don't contradict it.
If there is interest in this, I can move forward and make a PR with tests and doc...
It would be interesting for me, but i not a reviewer here:)
Side note, the member annotations can also be functions:
struct acquisition
{
int nb_frames = 1;
...
}
BOOST_ANNOTATE_MEMBER(acquisition, nb_frames,
(desc, "number of frames"),
(doc, "The number of frames to acquire (0 = continuous acquisition)"),
(validate, [](int nb_frames) { return nb_frames >= 0;}))
Optional annotation can be handled pretty easily. Here we have a validate
function annotation and a generic validate free function would use the annotation if available:
using Validate = BOOST_DESCRIBE_MAKE_NAME(validate);
template <typename L, typename T,
std::enable_if_t<boost::describe::has_annotation_by_name<L, Validate>::value, bool> = true>
bool validate(T const& t)
{
// With validation
auto validate = boost::describe::annotation_by_name_v<L, Validate>;
return validate(t);
}
template <typename L, typename T,
std::enable_if_t<!boost::describe::has_annotation_by_name<L, Validate>::value, bool> = false>
bool validate(T const& t)
{
// Without validation
return true;
}
Following up discussion on cpplang, it would be interesting to have a way to extend member descriptors with custom information (think units, text description). User-defined attributes seems the right tool to express this custom information:
The emulation of the reflection would look something like:
We would have
describe_member_attributes<D>
returnsL<A1, A2, …, An>
, whereD
is a member descriptor.and
Ai
is an attribute descriptor of the formAs pointed out by @pdimov, there is a proposal about attribute reflection that introduces typed attributes.