Overview
This library enables authors of user-defined types (enums, structs
and classes) to describe their enumerators, base classes, data members
and member functions. This information can later be queried by other
code portions, possibly written by a different author, using the
supplied primitives describe_enumerators
, describe_bases
, and
describe_members
.
To learn how to describe enumeration types, see Describing Enumeration Types.
To learn how to describe class types, including structs, see Describing Class Types.
For examples how this functionality is useful, see Usage Examples.
The purpose of the library is to establish a standard way of providing these reflection abilities. Many existing libraries provide their own way of describing enums or classes, but without a standard, code written by different people cannot interoperate.
Eventually, one might hope for the primitives to end up in the C++ standard, with the compiler automatically supplying the metadata necessary to describe the types, making manual macro annotation unnecessary.
Revision History
Changes in Boost 1.78.0
-
Added
has_describe_enumerators
,has_describe_bases
,has_describe_members
. -
Added
enum_to_string
,enum_from_string
. -
Added relational and stream insertion operators.
-
Added
descriptor_by_name
,descriptor_by_pointer
.
Describing Enumeration Types
If you have an enumeration type
enum E
{
v1 = 1,
v2 = 2,
v3 = 4,
};
you can add reflection metadata to it via the BOOST_DESCRIBE_ENUM
macro:
BOOST_DESCRIBE_ENUM(E, v1, v2, v3)
The macro is defined in <boost/describe/enum.hpp>
and should be placed in
the same namespace as the enum.
If your enumerators don’t have initializers, instead of repeating them
enum E2 { a, b, c, d };
BOOST_DESCRIBE_ENUM(E2, a, b, c, d)
you can use the convenience macro
BOOST_DEFINE_ENUM(E2, a, b, c, d)
which expands to the two previous lines.
For defining enum class E2
instead, use BOOST_DEFINE_ENUM_CLASS
. To add
an underlying type, i.e. enum E3: int
or enum class E4: unsigned char
,
use BOOST_DEFINE_FIXED_ENUM
and BOOST_DEFINE_FIXED_ENUM_CLASS
, respectively.
If your enumeration type is nested inside a class or a struct
, use the
BOOST_DESCRIBE_NESTED_ENUM
macro next to the enum
, as follows:
class X
{
private:
enum class E
{
v1,
v2
};
BOOST_DESCRIBE_NESTED_ENUM(E, v1, v2)
public:
// ...
};
Once an enumeration type E
is annotated, one can use describe_enumerators<E>
to obtain a descriptor list. (describe_enumerators
is defined in the
boost::describe
namespace, in <boost/describe/enumerators.hpp>
.)
A descriptor list is a type of the form L<D1, D2, …, Dn>
, where L
is of
the form template<class… T> struct L {};
and Di
is of the form
struct Di
{
static constexpr E value;
static constexpr char const* name;
};
To iterate over the descriptor list, you can
use mp_for_each
from Mp11:
boost::mp11::mp_for_each< boost::describe::describe_enumerators<E> >([](auto D){
std::printf( "%s: %d\n", D.name, D.value );
});
Describing Class Types
Class Types with Public Members
If you have a struct
struct X
{
int m1;
int m2;
};
use the BOOST_DESCRIBE_STRUCT
macro to describe it:
BOOST_DESCRIBE_STRUCT(X, (), (m1, m2))
BOOST_DESCRIBE_STRUCT
is defined in <boost/describe/class.hpp>
and should
be placed in the same namespace as the struct
.
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,
does not require access to the definition, and can therefore be used to describe
third-party types or types defined in system headers.
Class Types with Protected or Private Members
To describe a class type, use the BOOST_DESCRIBE_CLASS
macro instead, placing
it inside the class. This gives the macro access to the protected and private
members, but is intrusive and requires access to the definition.
class Y: private X
{
public:
int m3;
protected:
int m4;
private:
int m5;
public:
int f() const;
private:
BOOST_DESCRIBE_CLASS(Y, (X), (m3, f), (m4), (m5))
};
It takes three member lists, for the public, protected, and private members.
Retrieving Class Properties
Once a type T
is annotated, its properties can be retrieved via
describe_bases<T, M>
and describe_members<T, M>
(M
is a bitmask of
modifiers such as mod_public | mod_static | mod_function
).
These primitives are defined in namespace boost::describe
, in the headers
<boost/describe/bases.hpp>
and <boost/describe/members.hpp>
, respectively.
describe_bases
takes the following possible modifiers: mod_public
,
mod_protected
, mod_private
, or a bitwise-or combination of them. The
presence of mod_public
includes the public bases in the result, its absence
excludes them. The other two modifiers work similarly.
describe_members
takes a bitwise-or combination of the following possible
modifiers: mod_public
, mod_protected
, mod_private
, mod_static
,
mod_function
, mod_any_member
, mod_inherited
, mod_hidden
.
The access modifiers work the same as with describe_bases
.
(For types annotated with BOOST_DESCRIBE_STRUCT
, the protected and private
member lists will be empty.)
When mod_static
is present, the static members are returned, otherwise
the nonstatic members are returned.
When mod_function
is present, the member functions are returned, otherwise
the data members are returned.
When mod_any_member
is present, mod_static
and mod_function
are ignored
and all members are returned regardless of kind.
When mod_inherited
is present, members of base classes are also returned.
When mod_hidden
is present, hidden inherited members are included. A member
of a base class is hidden when a derived class has a member of the same name.
For the above class Y
, describe_bases<Y, mod_any_access>
will return a
type list L<D1>
containing a single base descriptor D1
describing X
:
struct D1
{
using type = X;
static constexpr unsigned modifiers = mod_private;
};
describe_members<Y, mod_private>
will return a type list L<D2>
containing
the descriptor of the data member Y::m5
:
struct D2
{
static constexpr int Y::* pointer = &Y::m5;
static constexpr char const * name = "m5";
static constexpr unsigned modifiers = mod_private;
};
For an example of how to use the base and data member descriptors, see Defining a Universal Print Function.
For an example of how to use member function descriptors, see Automatic JSON RPC.
Overloaded Member Functions
To describe an overloaded member function, you will need to resort to
a more complicated syntax, as simply listing its name (say, f
) will make
the library attempt to form a member pointer with &X::f
, which would fail
because it’s not clear to which f
this expression refers.
To disambiguate, precede the function name with the type of the function, in parentheses, as shown in the following example:
struct X
{
int f();
int f() const;
void f( int x );
};
BOOST_DESCRIBE_STRUCT(X, (), (
(int ()) f,
(int () const) f,
(void (int)) f
))
The type of the function is the same as its declaration, without the name.
Be sure to retain the space between the parenthesized function type and its name, because omitting it will compile happily on GCC and Clang but will lead to inscrutable errors on MSVC due to its nonstandard preprocessor.
Pay attention to the proper placement of the parentheses, because a mistake there will also lead to hard to decipher compiler errors, on all compilers.
The same technique also works with BOOST_DESCRIBE_CLASS
, and with static member
functions:
class Y
{
public:
static void f( int x );
static void f( int x, int y );
BOOST_DESCRIBE_CLASS(Y, (), ((void (int)) f, (void (int, int)) f), (), ())
};
The case where a member function and a static member function have the same name and the same function type is currently not supported.
Usage Examples
Printing Enumerators with a Compile Time Loop
A simple example that just iterates over the enumerator
descriptors using mp11::mp_for_each
and prints them.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <cstdio>
enum E
{
v1 = 11,
v2,
v3 = 5
};
BOOST_DESCRIBE_ENUM(E, v1, v2, v3)
int main()
{
boost::mp11::mp_for_each< boost::describe::describe_enumerators<E> >([](auto D){
std::printf( "%s: %d\n", D.name, D.value );
});
}
Printing Enumerators with a Run Time Loop
This is similar to the previous example, but it first builds a
std::array
with the values, and then iterates over it using
an ordinary for
loop, instead of mp_for_each
.
#include <boost/describe.hpp>
#include <cstdio>
#include <array>
template<class E> struct enum_descriptor
{
E value;
char const * name;
};
template<class E, template<class... T> class L, class... T>
constexpr std::array<enum_descriptor<E>, sizeof...(T)>
describe_enumerators_as_array_impl( L<T...> )
{
return { { { T::value, T::name }... } };
}
template<class E> constexpr auto describe_enumerators_as_array()
{
return describe_enumerators_as_array_impl<E>( boost::describe::describe_enumerators<E>() );
}
BOOST_DEFINE_ENUM(E, v1, v2, v3, v4, v5, v6)
int main()
{
constexpr auto D = describe_enumerators_as_array<E>();
for( auto const& x: D )
{
std::printf( "%s: %d\n", x.name, x.value );
}
}
enum_to_string
This example shows a function that, given an enumerator
value, returns its name. If the value does not correspond
to a named value, the function returns "(unnamed)"
.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
template<class E> char const * enum_to_string( E e )
{
char const * r = "(unnamed)";
boost::mp11::mp_for_each< boost::describe::describe_enumerators<E> >([&](auto D){
if( e == D.value ) r = D.name;
});
return r;
}
#include <iostream>
enum E
{
v1 = 3,
v2,
v3 = 11
};
BOOST_DESCRIBE_ENUM(E, v1, v2, v3)
int main()
{
std::cout << "E(" << v1 << "): " << enum_to_string( v1 ) << std::endl;
std::cout << "E(" << 0 << "): " << enum_to_string( E(0) ) << std::endl;
}
Since release 1.78.0, the library provides enum_to_string
.
It differs from the one in the example by having a second
parameter that determines what should be returned when the
value doesn’t correspond to a named enumerator.
string_to_enum
The opposite of the previous example; returns an enumerator value when given the enumerator name. When the string passed does not correspond to any enumerator name, throws an exception.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <stdexcept>
#include <typeinfo>
#include <string>
#include <cstring>
[[noreturn]] void throw_invalid_name( char const * name, char const * type )
{
throw std::runtime_error(
std::string( "Invalid enumerator name '" ) + name
+ "' for enum type '" + type + "'" );
}
template<class E> E string_to_enum( char const * name )
{
bool found = false;
E r = {};
boost::mp11::mp_for_each< boost::describe::describe_enumerators<E> >([&](auto D){
if( !found && std::strcmp( D.name, name ) == 0 )
{
found = true;
r = D.value;
}
});
if( found )
{
return r;
}
else
{
throw_invalid_name( name, typeid( E ).name() );
}
}
#include <iostream>
BOOST_DEFINE_ENUM(E, v1, v2, v3)
int main()
{
try
{
std::cout << "v1: " << string_to_enum<E>( "v1" ) << std::endl;
std::cout << "v2: " << string_to_enum<E>( "v2" ) << std::endl;
std::cout << "v3: " << string_to_enum<E>( "v3" ) << std::endl;
std::cout << "v4: " << string_to_enum<E>( "v4" ) << std::endl;
}
catch( std::exception const & x )
{
std::cout << x.what() << std::endl;
}
}
Since release 1.78.0, the library provides enum_from_string
.
It differs from the function in the example by signaling failure
by a bool
return value instead of using exceptions. The
enumerator value is assigned to the output argument.
Defining a Universal Print Function
This example defines a universal operator<<
that works on
any class or struct type that has been described with
BOOST_DESCRIBE_STRUCT
or BOOST_DESCRIBE_CLASS
.
It first prints the base classes, recursively, then prints all the members.
(A C cast is used to access private base classes. This is not as bad as it first appears, because we’re only inspecting the base class by printing its members, and doing so should not change its state and hence cannot violate its invariant.)
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <ostream>
using namespace boost::describe;
template<class T,
class Bd = describe_bases<T, mod_any_access>,
class Md = describe_members<T, mod_any_access>>
std::ostream& operator<<( std::ostream & os, T const & t )
{
os << "{";
bool first = true;
boost::mp11::mp_for_each<Bd>([&](auto D){
if( !first ) { os << ", "; } first = false;
using B = typename decltype(D)::type;
os << (B const&)t;
});
boost::mp11::mp_for_each<Md>([&](auto D){
if( !first ) { os << ", "; } first = false;
os << "." << D.name << " = " << t.*D.pointer;
});
os << "}";
return os;
}
struct X
{
int m1 = 1;
};
BOOST_DESCRIBE_STRUCT(X, (), (m1))
struct Y
{
int m2 = 2;
};
BOOST_DESCRIBE_STRUCT(Y, (), (m2))
class Z: public X, private Y
{
int m1 = 3;
int m2 = 4;
BOOST_DESCRIBE_CLASS(Z, (X, Y), (), (), (m1, m2))
};
#include <iostream>
int main()
{
std::cout << Z() << std::endl;
}
Since release 1.78.0, this universal operator<<
is supplied
by the library, in the boost::describe::operators
namespace.
It’s enabled by means of a using declaration in the namespace
containing the described application types, like in the example
below:
namespace app
{
struct X
{
int a = 1;
};
BOOST_DESCRIBE_STRUCT(X, (), (a))
using boost::describe::operators::operator<<;
}
Implementing hash_value
This example defines a universal hash_value
overload that
computes the hash value of an annotated struct or class. It
does so by iterating over the described bases and members and
calling boost::hash_combine
on each.
The overload is defined in namespace app
in order to apply
to all annotated classes also defined in app
.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/container_hash/hash.hpp>
#include <boost/variant2/variant.hpp>
#include <vector>
using namespace boost::describe;
namespace app
{
template<class T,
class Bd = describe_bases<T, mod_any_access>,
class Md = describe_members<T, mod_any_access>>
std::size_t hash_value( T const & t )
{
std::size_t r = 0;
boost::mp11::mp_for_each<Bd>([&](auto D){
using B = typename decltype(D)::type;
boost::hash_combine( r, (B const&)t );
});
boost::mp11::mp_for_each<Md>([&](auto D){
boost::hash_combine( r, t.*D.pointer );
});
return r;
}
struct A
{
int x = 1;
};
BOOST_DESCRIBE_STRUCT(A, (), (x))
struct B
{
int y = 2;
};
BOOST_DESCRIBE_STRUCT(B, (), (y))
struct C
{
std::vector<boost::variant2::variant<A, B>> v;
};
BOOST_DESCRIBE_STRUCT(C, (), (v))
} // namespace app
#include <iostream>
int main()
{
app::C c;
c.v.push_back( app::A{} );
c.v.push_back( app::B{} );
std::cout << boost::hash<app::C>()( c ) << std::endl;
}
Implementing operator==
This example defines a universal operator==
overload that
iterates over the described bases and members and compares
them for equality using ==
.
The overload is defined in namespace app
in order to apply
to all annotated classes also defined in app
.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/variant2/variant.hpp>
#include <vector>
using namespace boost::describe;
namespace app
{
template<class T,
class Bd = describe_bases<T, mod_any_access>,
class Md = describe_members<T, mod_any_access>>
bool operator==( T const& t1, T const& t2 )
{
bool r = true;
boost::mp11::mp_for_each<Bd>([&](auto D){
using B = typename decltype(D)::type;
r = r && (B const&)t1 == (B const&)t2;
});
boost::mp11::mp_for_each<Md>([&](auto D){
r = r && t1.*D.pointer == t2.*D.pointer;
});
return r;
}
struct A
{
int x = 1;
};
BOOST_DESCRIBE_STRUCT(A, (), (x))
struct B
{
int y = 2;
};
BOOST_DESCRIBE_STRUCT(B, (), (y))
struct C
{
std::vector<boost::variant2::variant<A, B>> v;
};
BOOST_DESCRIBE_STRUCT(C, (), (v))
} // namespace app
#include <iostream>
int main()
{
app::C c1, c2, c3;
c1.v.push_back( app::A{} );
c2.v.push_back( app::A{} );
c3.v.push_back( app::B{} );
std::cout << std::boolalpha
<< ( c1 == c2 ) << ' '
<< ( c1 == c3 ) << std::endl;
}
Since release 1.78.0, this universal operator==
is supplied
by the library, in the boost::describe::operators
namespace.
It’s enabled by means of a using declaration in the namespace
containing the described application types, like in the example
below:
namespace app
{
struct X
{
int a = 1;
};
BOOST_DESCRIBE_STRUCT(X, (), (a))
using boost::describe::operators::operator==;
}
The rest of the relational operators are also provided and can be enabled similarly.
struct_to_tuple
This example defines a function struct_to_tuple
that takes
a described class type as an argument and returns a tuple of
all its public members.
#include <boost/describe.hpp>
#include <tuple>
namespace desc = boost::describe;
template<class T, template<class...> class L, class... D>
auto struct_to_tuple_impl( T const& t, L<D...> )
{
return std::make_tuple( t.*D::pointer... );
}
template<class T, class Dm = desc::describe_members<T,
desc::mod_public | desc::mod_inherited>>
auto struct_to_tuple( T const& t )
{
return struct_to_tuple_impl( t, Dm() );
}
#include <boost/core/type_name.hpp>
#include <iostream>
struct X
{
int a = 1;
};
BOOST_DESCRIBE_STRUCT(X, (), (a))
struct Y
{
float b = 3.14f;
};
BOOST_DESCRIBE_STRUCT(Y, (), (b))
struct Z: X, Y
{
};
BOOST_DESCRIBE_STRUCT(Z, (X, Y), ())
int main()
{
Z z;
auto tp = struct_to_tuple( z );
std::cout <<
boost::core::type_name<decltype(tp)>() << ": "
<< std::get<0>(tp) << ", " << std::get<1>(tp);
}
Automatic Conversion to JSON
This example defines a universal tag_invoke
overload that
automatically converts an annotated struct to a
Boost.JSON value by iterating
over the described public members and adding them to the return
boost::json::object
.
The overload is defined in namespace app
in order to apply
to all annotated classes also defined in app
.
The presence of private members is taken as an indication that
a universal conversion is not suitable, so the overload is
disabled in this case using std::enable_if_t
.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/json.hpp>
#include <type_traits>
#include <vector>
#include <map>
namespace app
{
template<class T,
class D1 = boost::describe::describe_members<T,
boost::describe::mod_public | boost::describe::mod_protected>,
class D2 = boost::describe::describe_members<T, boost::describe::mod_private>,
class En = std::enable_if_t<boost::mp11::mp_empty<D2>::value> >
void tag_invoke( boost::json::value_from_tag const&, boost::json::value& v, T const & t )
{
auto& obj = v.emplace_object();
boost::mp11::mp_for_each<D1>([&](auto D){
obj[ D.name ] = boost::json::value_from( t.*D.pointer );
});
}
struct A
{
int x;
int y;
};
BOOST_DESCRIBE_STRUCT(A, (), (x, y))
struct B
{
std::vector<A> v;
std::map<std::string, A> m;
};
BOOST_DESCRIBE_STRUCT(B, (), (v, m))
} // namespace app
#include <iostream>
int main()
{
app::B b{ { { 1, 2 }, { 3, 4 } }, { { "k1", { 5, 6 } }, { "k2", { 7, 8 } } } };
std::cout << boost::json::value_from( b ) << std::endl;
}
Automatic Conversion from JSON
Like the previous example, but in the other direction. Defines
a tag_invoke
overload that converts a boost::json::value
to
an annotated struct.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/json.hpp>
#include <type_traits>
namespace app
{
template<class T> void extract( boost::json::object const & obj, char const * name, T & value )
{
value = boost::json::value_to<T>( obj.at( name ) );
}
template<class T,
class D1 = boost::describe::describe_members<T,
boost::describe::mod_public | boost::describe::mod_protected>,
class D2 = boost::describe::describe_members<T, boost::describe::mod_private>,
class En = std::enable_if_t<boost::mp11::mp_empty<D2>::value> >
T tag_invoke( boost::json::value_to_tag<T> const&, boost::json::value const& v )
{
auto const& obj = v.as_object();
T t{};
boost::mp11::mp_for_each<D1>([&](auto D){
extract( obj, D.name, t.*D.pointer );
});
return t;
}
struct A
{
int x;
int y;
};
BOOST_DESCRIBE_STRUCT(A, (), (x, y))
} // namespace app
#include <iostream>
int main()
{
boost::json::value jv{ { "x", 1 }, { "y", 2 } };
std::cout << "jv: " << jv << std::endl;
auto a = boost::json::value_to<app::A>( jv );
std::cout << "a: { " << a.x << ", " << a.y << " }" << std::endl;
}
Automatic Serialization
This example defines a universal serialize
function that
automatically adds Boost.Serialization
support to annotated classes.
#define _CRT_SECURE_NO_WARNINGS
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/core/nvp.hpp>
#include <type_traits>
#include <cstdio>
#include <vector>
namespace app
{
template<class Archive, class T,
class D1 = boost::describe::describe_bases<T, boost::describe::mod_public>,
class D2 = boost::describe::describe_bases<T,
boost::describe::mod_protected | boost::describe::mod_private>,
class D3 = boost::describe::describe_members<T,
boost::describe::mod_public | boost::describe::mod_protected>,
class D4 = boost::describe::describe_members<T, boost::describe::mod_private>,
class En = std::enable_if_t<
boost::mp11::mp_empty<D2>::value && boost::mp11::mp_empty<D4>::value> >
void serialize( Archive & ar, T & t, boost::serialization::version_type )
{
int k = 0;
// public bases: use base_object
boost::mp11::mp_for_each<D1>([&](auto D){
using B = typename decltype(D)::type;
char name[ 32 ];
std::sprintf( name, "base.%d", ++k );
ar & boost::make_nvp( name, boost::serialization::base_object<B>( t ) );
});
// public (and protected) members
boost::mp11::mp_for_each<D3>([&](auto D){
ar & boost::make_nvp( D.name, t.*D.pointer );
});
}
struct A1
{
int x;
};
BOOST_DESCRIBE_STRUCT(A1, (), (x))
struct A2
{
int y;
};
BOOST_DESCRIBE_STRUCT(A2, (), (y))
struct B: public A1, public A2
{
// these constructors aren't needed in C++17
B(): A1(), A2() {}
B( int x, int y ): A1{ x }, A2{ y } {}
};
BOOST_DESCRIBE_STRUCT(B, (A1, A2), ())
struct C
{
std::vector<B> v;
};
BOOST_DESCRIBE_STRUCT(C, (), (v))
} // namespace app
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <string>
#include <sstream>
#include <iostream>
int main()
{
app::C c1{ { { 1, 2 }, { 3, 4 }, { 5, 6 } } };
std::ostringstream os;
{
boost::archive::xml_oarchive ar( os );
ar << boost::make_nvp( "c1", c1 );
}
std::string s = os.str();
std::cout << s << std::endl;
app::C c2;
{
std::istringstream is( s );
boost::archive::xml_iarchive ar( is );
ar >> boost::make_nvp( "c2", c2 );
}
{
boost::archive::text_oarchive ar( std::cout );
ar << c2;
}
}
Automatic JSON RPC
This example defines a generic call
function that can be used to
invoke a member function by name, with the arguments passed in a
Boost.JSON array. The result is returned
in a boost::json::value
.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/json.hpp>
#include <boost/type_traits.hpp>
#include <boost/utility/string_view.hpp>
#include <stdexcept>
#include <string>
template<class C1, class C2, class R, class... A, std::size_t... I>
boost::json::value
call_impl_( C1 & c1, R (C2::*pmf)(A...), boost::json::array const & args,
std::index_sequence<I...> )
{
return boost::json::value_from(
(c1.*pmf)(
boost::json::value_to< boost::remove_cv_ref_t<A> >( args[ I ] )... ) );
}
template<class C1, class C2, class R, class... A>
boost::json::value
call_impl( C1 & c1, R (C2::*pmf)(A...), boost::json::array const & args )
{
if( args.size() != sizeof...(A) )
{
throw std::invalid_argument( "Invalid number of arguments" );
}
return call_impl_( c1, pmf, args, std::index_sequence_for<A...>() );
}
template<class C>
boost::json::value
call( C & c, boost::string_view method, boost::json::value const & args )
{
using Fd = boost::describe::describe_members<C,
boost::describe::mod_public | boost::describe::mod_function>;
bool found = false;
boost::json::value result;
boost::mp11::mp_for_each<Fd>([&](auto D){
if( !found && method == D.name)
{
result = call_impl( c, D.pointer, args.as_array() );
found = true;
}
});
if( !found )
{
throw std::invalid_argument( "Invalid method name" );
}
return result;
}
struct Object
{
std::string greet( std::string const & who )
{
return "Hello, " + who + "!";
}
int add( int x, int y )
{
return x + y;
}
};
BOOST_DESCRIBE_STRUCT(Object, (), (greet, add))
#include <iostream>
int main()
{
Object obj;
std::cout << call( obj, "greet", { "world" } ) << std::endl;
std::cout << call( obj, "add", { 1, 2 } ) << std::endl;
}
Interactive Variable Console
This example implements an interactive console that allows printing and modifying variables. It uses Boost.JSON for converting the variables to and from a string form.
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/json.hpp>
#include <boost/utility/string_view.hpp>
#include <string>
#include <stdexcept>
#include <vector>
#include <map>
#include <iostream>
// get variable
template<class Scope> boost::json::value get( Scope& scope, boost::string_view name )
{
using Md = boost::describe::describe_members<Scope, boost::describe::mod_public>;
bool found = false;
boost::json::value result;
boost::mp11::mp_for_each<Md>([&](auto D) {
if( !found && name == D.name )
{
result = boost::json::value_from( scope.*D.pointer );
found = true;
}
});
if( !found )
{
throw std::invalid_argument(
std::string( "'" ) + std::string( name ) + "': no such variable" );
}
return result;
}
// set variable
template<class T> void set_impl( T & t, boost::string_view /*name*/, boost::json::value const& value )
{
t = boost::json::value_to<T>( value );
}
template<class T> void set_impl( T const & /*t*/, boost::string_view name, boost::json::value const& /*value*/ )
{
throw std::invalid_argument(
std::string( "'" ) + std::string( name ) + "': variable cannot be modified" );
}
template<class Scope> void set( Scope& scope, boost::string_view name, boost::json::value const& value )
{
using Md = boost::describe::describe_members<Scope, boost::describe::mod_public>;
bool found = false;
boost::mp11::mp_for_each<Md>([&](auto D) {
if( !found && name == D.name )
{
set_impl( scope.*D.pointer, name, value );
found = true;
}
});
if( !found )
{
throw std::invalid_argument(
std::string( "'" ) + std::string( name ) + "': no such variable" );
}
}
//
struct globals
{
std::string const help = "Enter a variable name ('x', 'y', 'v', or 'm') to print its value; enter variable=value to assign a new value to a variable. Values are in JSON format.";
int x = 1;
double y = 3.14;
std::vector<int> v{ 1, 2, 3 };
std::map<std::string, double> m{ { "BTC", 44898.68 }, { "ETH", 1386.57 } };
};
BOOST_DESCRIBE_STRUCT( globals, (), (help, x, y, v, m) )
int main()
{
globals g_;
for( ;; )
{
std::cout << "\n> ";
std::string line;
std::getline( std::cin, line );
try
{
std::size_t i = line.find( '=' );
if( i != std::string::npos )
{
set( g_, line.substr( 0, i ), boost::json::parse( line.substr( i + 1 ) ) );
}
else
{
std::cout << get( g_, line ) << std::endl;
}
}
catch( std::exception const& x )
{
std::cout << "Error: " << x.what() << std::endl;
}
}
}
Implementation Features
Dependencies
Supported Compilers
-
GCC 5 or later with
-std=c++14
or above -
Clang 3.9 or later with
-std=c++14
or above -
Visual Studio 2015, 2017, 2019
Tested on Github Actions and Appveyor.
Limitations
This implementation has the following limitations:
-
Up to 52 elements are supported in the lists of enumerators, bases, and members.
-
Protected base classes cannot be distinguished from private base classes when the described class is final, and are considered private.
-
Bitfields are not supported. It’s not possible to form a pointer to member to a bitfield.
-
Reference members are not supported. It’s not possible to form a pointer to member to a reference.
-
Anonymous unions are not supported.
Reference
<boost/describe/enum.hpp>
#define BOOST_DESCRIBE_ENUM(E, ...) /*...*/
#define BOOST_DESCRIBE_NESTED_ENUM(E, ...) /*...*/
#define BOOST_DEFINE_ENUM(E, ...) \
enum E { __VA_ARGS__ }; BOOST_DESCRIBE_ENUM(E, __VA_ARGS__)
#define BOOST_DEFINE_ENUM_CLASS(E, ...) \
enum class E { __VA_ARGS__ }; BOOST_DESCRIBE_ENUM_CLASS(E, __VA_ARGS__)
#define BOOST_DEFINE_FIXED_ENUM(E, Base, ...) \
enum E: Base { __VA_ARGS__ }; BOOST_DESCRIBE_ENUM(E, __VA_ARGS__)
#define BOOST_DEFINE_FIXED_ENUM_CLASS(E, Base, ...) \
enum class E: Base { __VA_ARGS__ }; BOOST_DESCRIBE_ENUM_CLASS(E, __VA_ARGS__)
BOOST_DESCRIBE_ENUM
BOOST_DESCRIBE_ENUM(E, v1, v2, …, vN)
should be placed in the namespace
where the enumeration type E
is defined, and creates the necessary metadata
for describe_enumerators<E>
to work.
After this macro is used, describe_enumerators<E>
returns L<D1, D2, …, Dn>
,
where L
is a class template of the form
template<class...> struct L {};
and Di
is an enumerator descriptor of the form
struct Di
{
static constexpr E value = vi;
static constexpr char const * name = "vi";
};
where vi
is the corresponding identifier passed to the macro.
BOOST_DESCRIBE_NESTED_ENUM
BOOST_DESCRIBE_NESTED_ENUM(E, v1, v2, …, vN)
is similar to
BOOST_DESCRIBE_ENUM
and is used to annotate enumeration types nested inside
class (or struct
) types. It should be placed in the class type where the
enum
is defined.
BOOST_DEFINE_ENUM
BOOST_DEFINE_ENUM(E, v1, v2, …, vN)
is a convenience macro expanding to
enum E { v1, v2, ..., vN };
BOOST_DESCRIBE_ENUM(E, v1, v2, ..., vN)
BOOST_DEFINE_ENUM_CLASS
BOOST_DEFINE_ENUM_CLASS(E, v1, v2, …, vN)
is a convenience macro expanding to
enum class E { v1, v2, ..., vN };
BOOST_DESCRIBE_ENUM(E, v1, v2, ..., vN)
BOOST_DEFINE_FIXED_ENUM
BOOST_DEFINE_FIXED_ENUM(E, Base, v1, v2, …, vN)
is a convenience macro expanding to
enum E: Base { v1, v2, ..., vN };
BOOST_DESCRIBE_ENUM(E, v1, v2, ..., vN)
BOOST_DEFINE_FIXED_ENUM_CLASS
BOOST_DEFINE_FIXED_ENUM_CLASS(E, Base, v1, v2, …, vN)
is a convenience macro expanding to
enum class E: Base { v1, v2, ..., vN };
BOOST_DESCRIBE_ENUM(E, v1, v2, ..., vN)
<boost/describe/enumerators.hpp>
namespace boost {
namespace describe {
template<class E> using describe_enumerators = /*...*/;
template<class E> using has_describe_enumerators = /*...*/;
} }
describe_enumerators<E>
describe_enumerators<E>
returns L<D1, D2, …, Dn>
, where L
is a class
template of the form
template<class...> struct L {};
and Di
is an enumerator descriptor of the form
struct Di
{
static constexpr E value = vi;
static constexpr char const * name = "vi";
};
where vi
is the i-th enumerator.
If E
is not a described enumeration type, describe_enumerators<E>
causes
a substitution failure.
has_describe_enumerators<E>
has_describe_enumerators<E>::value
is true
when E
is a described
enumeration type, false
otherwise.
<boost/describe/class.hpp>
#define BOOST_DESCRIBE_STRUCT(Name, Bases, Members) /*...*/
#define BOOST_DESCRIBE_CLASS(Name, Bases, Public, Protected, Private) /*...*/
BOOST_DESCRIBE_STRUCT
BOOST_DESCRIBE_STRUCT
should be placed in the same namespace as the struct
type being described, and takes three arguments: name of the struct type,
a parentheses-enclosed list of base classes, and a parentheses-enclosed list
of public members.
Example:
struct X
{
};
BOOST_DESCRIBE_STRUCT(X, (), ())
struct Y: public X
{
int m;
static void f();
};
BOOST_DESCRIBE_STRUCT(Y, (X), (m, f))
BOOST_DESCRIBE_CLASS
BOOST_DESCRIBE_CLASS
should be placed inside the class definition of the
described type, and takes five arguments: the name of the class, a list of
base classes, a list of public members, a list of protected members, and a
list of private members.
Example:
class X
{
int m1;
BOOST_DESCRIBE_CLASS(X, (), (), (), (m1))
};
class Y: private X
{
public:
int m1;
void f() const {}
protected:
int m2;
private:
int m3;
BOOST_DESCRIBE_CLASS(Y, (X), (m1, f), (m2), (m3))
};
<boost/describe/modifiers.hpp>
namespace boost
{
namespace describe
{
enum modifiers
{
mod_public = 1,
mod_protected = 2,
mod_private = 4,
mod_virtual = 8,
mod_static = 16,
mod_function = 32,
mod_any_member = 64,
mod_inherited = 128,
mod_hidden = 256,
};
constexpr modifiers mod_any_access = static_cast<modifiers>( mod_public | mod_protected | mod_private );
} // namespace describe
} // namespace boost
modifiers
The enumeration type modifiers
is a bitmask type that contains the
following flags:
-
mod_public
- includes public bases or members in the descriptor list -
mod_protected
- includes protected bases or members -
mod_private
- includes private bases or members -
mod_virtual
- returned when a base class is a virtual base -
mod_static
- returns static members (when not given, returns nonstatic members) -
mod_function
- returns member functions (when not given, returns data members) -
mod_any_member
- overridesmod_static
andmod_function
and returns all members regardless of kind -
mod_inherited
- includes members of base classes -
mod_hidden
- includes hidden inherited members
<boost/describe/bases.hpp>
namespace boost {
namespace describe {
template<class T, unsigned M> using describe_bases = /*...*/;
template<class T> using has_describe_bases = /*...*/;
} }
describe_bases<T, M>
M
must be a bitwise-or combination of mod_public
, mod_protected
, and
mod_private
, and acts as a filter.
describe_bases<T, M>
returns L<D1, D2, …, Dn>
, where L
is a class
template of the form
template<class...> struct L {};
and Di
is a base descriptor of the form
struct Di
{
using type = /*...*/;
static constexpr unsigned modifiers = /*...*/;
};
where type
is the type of the base class, and modifiers
are a bitwise-or
combination of mod_public
, mod_protected
, mod_private
, and mod_virtual
that reflects the properties of the base class.
If T
is not a described class type, describe_bases<T, M>
causes a
substitution failure.
has_describe_bases<T>
has_describe_bases<T>::value
is true
when T
is a described class type,
false
otherwise.
Since the library does not provide a way to describe bases and members separately,
has_describe_bases
and has_describe_members
are, in practice, synonyms. They
are provided separately for consistency.
<boost/describe/members.hpp>
namespace boost {
namespace describe {
template<class T, unsigned M> using describe_members = /*...*/;
template<class T> using has_describe_members = /*...*/;
} }
describe_members<T, M>
M
must be a bitwise-or combination of mod_public
, mod_protected
,
mod_private
, mod_static
, mod_function
, mod_any_member
,
mod_inherited
, and mod_hidden
, and acts as a filter.
describe_members<T, M>
returns L<D1, D2, …, Dn>
, where L
is a class
template of the form
template<class...> struct L {};
and Di
is a member descriptor of the form
struct Di
{
static constexpr auto pointer = &T::m;
static constexpr char const * name = "m";
static constexpr unsigned modifiers = /*...*/;
};
where pointer
is a pointer to member (for nonstatic members) or a pointer
(for static members) identifying the class member, name
is the name of the
member, and modifiers
are a bitwise-or combination of mod_public
,
mod_protected
, mod_private
, mod_static
, mod_function
, mod_inherited
,
and mod_hidden
that reflects the properties of the member.
If T
is not a described class type, describe_members<T, M>
causes a
substitution failure.
has_describe_members<T>
has_describe_members<T>::value
is true
when T
is a described class type,
false
otherwise.
Since the library does not provide a way to describe bases and members separately,
has_describe_bases
and has_describe_members
are, in practice, synonyms. They
are provided separately for consistency.
<boost/describe/enum_to_string.hpp>
namespace boost {
namespace describe {
template<class E> char const * enum_to_string( E e, char const * def ) noexcept;
} }
enum_to_string
The function enum_to_string
returns the name of the enumerator e
. E
must
be a described enumeration type. If e
does not correspond to one of the described
values, the function returns def
.
<boost/describe/enum_from_string.hpp>
namespace boost {
namespace describe {
template<class E> bool enum_from_string( char const * name, E & e ) noexcept;
} }
enum_from_string
The function enum_from_string
assigns to e
the enumerator value corresponding
to name
and returns true
. E
must be a described enumeration type. If name
does not correspond to one of the described values, the function returns false
.
<boost/describe/operators.hpp>
namespace boost {
namespace describe {
namespace operators {
template<class T> bool operator==( T const& t1, T const& t2 );
template<class T> bool operator!=( T const& t1, T const& t2 );
template<class T> bool operator<( T const& t1, T const& t2 );
template<class T> bool operator>( T const& t1, T const& t2 );
template<class T> bool operator<=( T const& t1, T const& t2 );
template<class T> bool operator>=( T const& t1, T const& t2 );
template<class T, class Ch, class Tr>
std::basic_ostream<Ch, Tr>&
operator<<( std::basic_ostream<Ch, Tr>& os, T const& t );
} } }
The header <boost/describe/operators.hpp>
defines generic operators for
described class types. They are used by bringing them into the namespace
containing the described types via a using declaration, as in the example
below:
namespace app
{
struct X
{
int a = 1;
};
BOOST_DESCRIBE_STRUCT(X, (), (a))
using boost::describe::operators::operator==;
using boost::describe::operators::operator!=;
using boost::describe::operators::operator<<;
}
operator==
If all bases and members compare equal, returns true
, otherwise false
.
operator!=
Returns the negation of operator==
.
operator<
Performs a lexicographical comparison over the bases and members in sequence
using operator<
and returns the result.
operator>
Returns the result of operator<
with the arguments reversed.
operator<=
Returns the negated result of operator>
.
operator>=
Returns the negated result of operator<
.
operator<<
Outputs a representation of t
to os
by recursively using operator<<
to output all bases and then all members.
<boost/describe/descriptor_by_name.hpp>
namespace boost {
namespace describe {
#define BOOST_DESCRIBE_MAKE_NAME(s) /*...*/
template<class L, class N> using descriptor_by_name = /*...*/;
} }
BOOST_DESCRIBE_MAKE_NAME
The macro BOOST_DESCRIBE_MAKE_NAME
creates a type that identifies the
name given as an argument. It should be used as follows:
using N = BOOST_DESCRIBE_MAKE_NAME(some_member);
descriptor_by_name
descriptor_by_name<L, N>
searches the descriptor list L
for the member
with the name identified by N
. N
should be a type created by
BOOST_DESCRIBE_MAKE_NAME
as in the above example. L
is intended to be
a list returned by describe_members
, although since enumerator descriptors
also have ::name
, a list returned by describe_enumerators
will work as
well.
using L = describe_members<SomeType, mod_any_access>;
using N = BOOST_DESCRIBE_MAKE_NAME(some_member);
using D = descriptor_by_name<L, N>; // descriptor for SomeType::some_member
<boost/describe/descriptor_by_pointer.hpp>
namespace boost {
namespace describe {
template<class L, auto Pm> using descriptor_by_pointer = /*...*/;
} }
descriptor_by_pointer
descriptor_by_pointer<L, Pm>
searches the descriptor list L
for the member
pointer Pm
. L
should be a list returned by describe_members
.
Since auto
template parameters are a C++17 feature, using
descriptor_by_pointer
requires C++17.
using L = describe_members<X, mod_any_access>;
using D = descriptor_by_pointer<L, &X::a>; // descriptor for X::a
<boost/describe.hpp>
This convenience header includes all the headers previously described.
Copyright and License
This documentation is copyright 2020, 2021 Peter Dimov and is distributed under the Boost Software License, Version 1.0.