diff --git a/doc/describe/classes.adoc b/doc/describe/classes.adoc index 811189c..b7120e9 100644 --- a/doc/describe/classes.adoc +++ b/doc/describe/classes.adoc @@ -33,7 +33,7 @@ It takes three arguments: the `struct` name, a list of base classes (empty in our example), and a list of (public) members by name (this includes both data members and member functions.) -Since `BOOST_DESCRIBE_STRUCT` is placed outside the type, it's non-intrisive, +Since `BOOST_DESCRIBE_STRUCT` is placed outside the type, it's non-intrusive, does not require access to the definition, and can therefore be used to describe third-party types or types defined in system headers. @@ -187,3 +187,31 @@ public: The case where a member function and a static member function have the same name and the same function type is currently not supported. + + +## Avoiding empty variadic macro + +The `BOOST_DESCRIBE_STRUCT` and `BOOST_DESCRIBE_CLASS` utilize empty variadic +macro arguments for missing bases or members, which relies on a C++20 +preprocessor extension. + +If this behavior is undesired or unsupported by your toolchain, +you can avoid it by explicitly specifying `void` for base classes +in simple cases, and by using the lower-level `BOOST_DESCRIBE_...` +macros for classes, as shown below: + +```_EMPTY +struct X { int foo; }; +BOOST_DESCRIBE_STRUCT(X, (void), (foo)) + +class Y { + public: + int foo; + private: + int bar; + friend BOOST_DESCRIBE_BASES(Y, void) + friend BOOST_DESCRIBE_PUBLIC_MEMBERS(Y, foo) + friend BOOST_DESCRIBE_PROTECTED_MEMBERS_EMPTY(Y) // use _EMPTY suffix for missing member groups + friend BOOST_DESCRIBE_PRIVATE_MEMBERS(Y, bar) +}; +``` diff --git a/include/boost/describe/class.hpp b/include/boost/describe/class.hpp index 8d3fdcb..90f9cec 100644 --- a/include/boost/describe/class.hpp +++ b/include/boost/describe/class.hpp @@ -37,8 +37,8 @@ namespace describe static_assert(std::is_class::value || std::is_union::value, "BOOST_DESCRIBE_STRUCT should only be used with class types"); \ BOOST_DESCRIBE_BASES(C, BOOST_DESCRIBE_PP_UNPACK Bases) \ BOOST_DESCRIBE_PUBLIC_MEMBERS(C, BOOST_DESCRIBE_PP_UNPACK Members) \ - BOOST_DESCRIBE_PROTECTED_MEMBERS(C) \ - BOOST_DESCRIBE_PRIVATE_MEMBERS(C) + BOOST_DESCRIBE_PROTECTED_MEMBERS_EMPTY(C) \ + BOOST_DESCRIBE_PRIVATE_MEMBERS_EMPTY(C) #else @@ -63,8 +63,8 @@ namespace describe static_assert(std::is_class::value || std::is_union::value, "BOOST_DESCRIBE_STRUCT should only be used with class types"); \ BOOST_DESCRIBE_MAYBE_UNUSED BOOST_DESCRIBE_BASES_(C BOOST_DESCRIBE_PP_UNPACK Bases) \ BOOST_DESCRIBE_MAYBE_UNUSED BOOST_DESCRIBE_PUBLIC_MEMBERS_(C BOOST_DESCRIBE_PP_UNPACK Members) \ - BOOST_DESCRIBE_MAYBE_UNUSED BOOST_DESCRIBE_PROTECTED_MEMBERS_(C) \ - BOOST_DESCRIBE_MAYBE_UNUSED BOOST_DESCRIBE_PRIVATE_MEMBERS_(C) + BOOST_DESCRIBE_MAYBE_UNUSED BOOST_DESCRIBE_PROTECTED_MEMBERS_EMPTY(C) \ + BOOST_DESCRIBE_MAYBE_UNUSED BOOST_DESCRIBE_PRIVATE_MEMBERS_EMPTY(C) #endif diff --git a/include/boost/describe/detail/bases.hpp b/include/boost/describe/detail/bases.hpp index d4fe494..b659320 100644 --- a/include/boost/describe/detail/bases.hpp +++ b/include/boost/describe/detail/bases.hpp @@ -26,6 +26,13 @@ template struct base_descriptor static constexpr unsigned modifiers = compute_base_modifiers(); }; +template +struct base_descriptor +{ + using type = void; + static constexpr unsigned modifiers = 0U; +}; + #ifndef __cpp_inline_variables template constexpr unsigned base_descriptor::modifiers; #endif diff --git a/include/boost/describe/detail/members.hpp b/include/boost/describe/detail/members.hpp index 8be5387..0e2a7ae 100644 --- a/include/boost/describe/detail/members.hpp +++ b/include/boost/describe/detail/members.hpp @@ -77,6 +77,15 @@ template constexpr auto mfn( F * p ) { return p; } #endif +#define BOOST_DESCRIBE_PUBLIC_MEMBERS_EMPTY(C) inline auto boost_public_member_descriptor_fn( C** ) \ +{ return boost::describe::detail::member_descriptor_fn_impl( 0 ); } + +#define BOOST_DESCRIBE_PROTECTED_MEMBERS_EMPTY(C) inline auto boost_protected_member_descriptor_fn( C** ) \ +{ return boost::describe::detail::member_descriptor_fn_impl( 0 ); } + +#define BOOST_DESCRIBE_PRIVATE_MEMBERS_EMPTY(C) inline auto boost_private_member_descriptor_fn( C** ) \ +{ return boost::describe::detail::member_descriptor_fn_impl( 0 ); } + } // namespace detail } // namespace describe } // namespace boost diff --git a/test/Jamfile b/test/Jamfile index 194d86b..649f94e 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -85,6 +85,8 @@ run pedantic_members_test.cpp run enum_from_string_test2.cpp ; +run empty_descriptor_test.cpp ; + # examples obj describe_cxx14 : describe_cxx14.cpp ; diff --git a/test/empty_descriptor_test.cpp b/test/empty_descriptor_test.cpp new file mode 100644 index 0000000..40e563d --- /dev/null +++ b/test/empty_descriptor_test.cpp @@ -0,0 +1,71 @@ +// Copyright 2022 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#if !defined(BOOST_DESCRIBE_CXX14) + +#include + +BOOST_PRAGMA_MESSAGE("Skipping test because C++14 is not available") +int main() {} + +#else + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic error "-Wgnu-zero-variadic-macro-arguments" +#endif + + +// Using (void) to specify empty bases for simple structs +struct X { int foo; }; +BOOST_DESCRIBE_STRUCT(X, (void), (foo)) + +// Using underlying macros for classes or empty types +class Y { + public: + int foo; + private: + int bar; + friend BOOST_DESCRIBE_BASES(Y, void) + friend BOOST_DESCRIBE_PUBLIC_MEMBERS(Y, foo) + friend BOOST_DESCRIBE_PROTECTED_MEMBERS_EMPTY(Y) + friend BOOST_DESCRIBE_PRIVATE_MEMBERS(Y, bar) +}; + + +int main() +{ + using namespace boost::describe; + using namespace boost::mp11; + + { + using L = describe_bases; + BOOST_TEST_EQ( mp_size::value, 0 ); + } + + { + using L = describe_members; + BOOST_TEST_EQ( mp_size::value, 1 ); + } + + { + using L = describe_bases; + BOOST_TEST_EQ( mp_size::value, 0 ); + } + + { + using L = describe_members; + BOOST_TEST_EQ( mp_size::value, 2 ); + } + + return boost::report_errors(); +} + + + +#endif