Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

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.81.0

  • To allow the inclusion of enumerators.hpp, bases.hpp, and members.hpp when the option -pedantic is used, the invocation of BOOST_DESCRIBE_ENUM has been moved from modifiers.hpp into a separate header, modifier_description.hpp. As a consequence, modifiers.hpp no longer includes enum.hpp. Code that has been relying on this implicit inclusion may fail, and will need to be fixed to include enum.hpp.

Changes in Boost 1.79.0

  • Enabled unions in BOOST_DESCRIBE_STRUCT and updated examples to check std::is_union<T>.

  • Added example of defining a fmtlib class formatter.

  • Added example of defining a fmtlib enum formatter.

  • Added example of printing pointers to members.

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.

  • Added struct_to_tuple example.

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 );

    });
}

Sample output:

v1: 11
v2: 12
v3: 5

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 );
    }
}

Sample output:

v1: 0
v2: 1
v3: 2
v4: 3
v5: 4
v6: 5

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;
}

Sample output:

E(3): v1
E(0): (unnamed)

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;
    }
}

Sample output:

v1: 0
v2: 1
v3: 2
v4: Invalid enumerator name 'v4' for enum type 'enum E'

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>,
    class En = std::enable_if_t<!std::is_union<T>::value> >
    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;
}

Sample output:

{{.m1 = 1}, {.m2 = 2}, .m1 = 3, .m2 = 4}

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.

Note
Since release 1.81.0, Boost.ContainerHash provides its own, built-in, support for described classes, by having an overload of boost::hash_value very similar to the one given below. Consequently, when using Boost 1.81.0 or later, you don’t need the below hash_value function, as it will cause an ambiguity.
#include <boost/container_hash/hash.hpp>
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/variant2/variant.hpp>
#include <boost/version.hpp>
#include <vector>

using namespace boost::describe;

namespace app
{

#if BOOST_VERSION < 108100

template<class T,
    class Bd = describe_bases<T, mod_any_access>,
    class Md = describe_members<T, mod_any_access>,
    class En = std::enable_if_t<!std::is_union<T>::value> >
    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;
}

#endif

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;
}

Sample output:

12526671134390370097

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>,
    class En = std::enable_if_t<!std::is_union<T>::value> >
    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;
}

Sample output:

true false

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>,
    class En = std::enable_if_t<!std::is_union<T>::value> >
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);
}

Sample output:

std::tuple<int, float>: 1, 3.14

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 && !std::is_union<T>::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;
}

Sample output:

{"v":[{"x":1,"y":2},{"x":3,"y":4}],"m":{"k1":{"x":5,"y":6},"k2":{"x":7,"y":8}}}

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 && !std::is_union<T>::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;
}

Sample output:

jv: {"x":1,"y":2}
a: { 1, 2 }

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 && !std::is_union<T>::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;
    }
}

Sample output:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="19">
<c1 class_id="0" tracking_level="0" version="0">
        <v class_id="1" tracking_level="0" version="0">
                <count>3</count>
                <item_version>0</item_version>
                <item class_id="2" tracking_level="0" version="0">
                        <base.1 class_id="3" tracking_level="0" version="0">
                                <x>1</x>
                        </base.1>
                        <base.2 class_id="4" tracking_level="0" version="0">
                                <y>2</y>
                        </base.2>
                </item>
                <item>
                        <base.1>
                                <x>3</x>
                        </base.1>
                        <base.2>
                                <y>4</y>
                        </base.2>
                </item>
                <item>
                        <base.1>
                                <x>5</x>
                        </base.1>
                        <base.2>
                                <y>6</y>
                        </base.2>
                </item>
        </v>
</c1>
</boost_serialization>


22 serialization::archive 19 0 0 0 0 3 0 0 0 0 0 1 0 0 2 3 4 5 6

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;
}

Sample output:

"Hello, world!"
3

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;
        }
    }
}

Sample output:

> 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."

> x
1

> y
3.14E0

> v
[1,2,3]

> m
{"BTC":4.489868E4,"ETH":1.38657E3}

> x="hello"
Error: not a number [boost.json:15]

> x=3.14
Error: not exact [boost.json:16]

> x=4

> x
4

> y=6.28

> y
6.28E0

> v=["hello", "world"]
Error: not a number [boost.json:15]

> v=[1.2, 3.4]
Error: not exact [boost.json:16]

> v=[11, 27]

> c
Error: 'c': no such variable

> v
[11,27]

> m={"BTC": 42139.07, "ETH": 2912.00}

> m
{"BTC":4.213907E4,"ETH":2.912E3}

fmtlib Class Formatter

This example defines a universal fmtlib formatter that works on any class or struct type that has been described with BOOST_DESCRIBE_STRUCT or BOOST_DESCRIBE_CLASS. It’s similar to the universal print function shown above.

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <fmt/format.h>
#include <type_traits>

template<class T> struct fmt::formatter<T, char, std::enable_if_t<
    boost::describe::has_describe_bases<T>::value &&
    boost::describe::has_describe_members<T>::value &&
    !std::is_union<T>::value>>
{
    constexpr auto parse( format_parse_context& ctx )
    {
        auto it = ctx.begin(), end = ctx.end();

        if( it != end && *it != '}' )
        {
            ctx.error_handler().on_error( "invalid format" );
        }

        return it;
    }

    auto format( T const& t, format_context& ctx ) const
    {
        using namespace boost::describe;

        using Bd = describe_bases<T, mod_any_access>;
        using Md = describe_members<T, mod_any_access>;

        auto out = ctx.out();

        *out++ = '{';

        bool first = true;

        boost::mp11::mp_for_each<Bd>([&](auto D){

            if( !first )
            {
                *out++ = ',';
            }

            first = false;

            out = fmt::format_to( out, " {}",
                (typename decltype(D)::type const&)t );
        });

        boost::mp11::mp_for_each<Md>([&](auto D){

            if( !first )
            {
                *out++ = ',';
            }

            first = false;

            out = fmt::format_to( out, " .{}={}",
                D.name, t.*D.pointer );
        });

        if( !first )
        {
            *out++ = ' ';
        }

        *out++ = '}';

        return out;
    }
};

struct point
{
    int x, y;
};

BOOST_DESCRIBE_STRUCT( point, (), (x, y) )

struct color
{
    unsigned char r, g, b;
};

BOOST_DESCRIBE_STRUCT( color, (), (r, g, b) )

struct line: color
{
    point first, last;
};

BOOST_DESCRIBE_STRUCT( line, (color), (first, last) )

int main()
{
    fmt::print( "{}\n", line{ { 255, 192, 16 }, { 1, 2 }, { 3, 4 } } );
}

Sample output:

{ { .r=255, .g=192, .b=16 }, .first={ .x=1, .y=2 }, .last={ .x=3, .y=4 } }

fmtlib Enum Formatter

This example defines a fmtlib formatter for described enums.

#include <boost/describe.hpp>
#include <fmt/format.h>
#include <type_traits>

template<class T> struct fmt::formatter<T, char, std::enable_if_t<
    boost::describe::has_describe_enumerators<T>::value>>
{
private:

    using U = std::underlying_type_t<T>;

    fmt::formatter<fmt::string_view, char> sf_;
    fmt::formatter<U, char> nf_;

public:

    constexpr auto parse( format_parse_context& ctx )
    {
        auto i1 = sf_.parse( ctx );
        auto i2 = nf_.parse( ctx );

        if( i1 != i2 )
        {
            ctx.error_handler().on_error( "invalid format" );
        }

        return i1;
    }

    auto format( T const& t, format_context& ctx ) const
    {
        char const * s = boost::describe::enum_to_string( t, 0 );

        if( s )
        {
            return sf_.format( s, ctx );
        }
        else
        {
            return nf_.format( static_cast<U>( t ), ctx );
        }
    }
};

enum E1
{
    v1, v2, v3 = 11
};

BOOST_DESCRIBE_ENUM( E1, v1, v2, v3 )

int main()
{
    fmt::print( "{:_^10}\n", E1::v1 );
    fmt::print( "{:_^10}\n", (E1)7 );
}

Sample output:

____v1____
____7_____

Printing Pointers to Members

This example defines an operator<< overload for pointers to members.

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/core/type_name.hpp>
#include <string>
#include <ostream>

namespace detail
{

template<class T1, class T2>
constexpr auto equals( T1 t1, T2 t2, int ) -> decltype( t1 == t2 )
{
    return t1 == t2;
}

template<class T1, class T2>
constexpr bool equals( T1 /*t1*/, T2 /*t2*/, long )
{
    return false;
}

using namespace boost::describe;

template<class T, class C,
    class L = describe_members<C, mod_any_access | mod_any_member>>
char const* get_member_name( T C::* pm, int )
{
    char const * name = nullptr;

    boost::mp11::mp_for_each<L>([&](auto D){

        if( equals( D.pointer, pm, 0 ) ) name = D.name;

    });

    return name;
}

template<class T, class C>
char const* get_member_name( T C::* /*pm*/, long )
{
    return nullptr;
}

} // namespace detail

template<class T, class C>
std::string pm_to_string( T C::* pm )
{
    char const * name = ::detail::get_member_name( pm, 0 );

    if( name == nullptr ) name = "(unknown)";

    return "&" + boost::core::type_name<C>() + "::" + name
        + " [" + boost::core::type_name<T>() + "]";
}

template<class T, class C>
std::ostream& operator<<( std::ostream& os, T C::* pm )
{
    os << pm_to_string( pm );
    return os;
}

struct X
{
    int m;
    int f() const { return m; }
};

BOOST_DESCRIBE_STRUCT(X, (), (m, f))

struct Y: public X
{
    int m;
    int g() const { return -m; }
};

BOOST_DESCRIBE_STRUCT(Y, (X), (m, g))

struct Z
{
    void h() {}
};

#if !defined(_MSC_VER) || defined(__clang__)

// MSVC doesn't support BOOST_DESCRIBE_CLASS inside
// templates until 2022 in C++20 mode

template<class T1, class T2> struct pair
{
    T1 first;
    T2 second;

    BOOST_DESCRIBE_CLASS(pair, (), (first, second), (), ())
};

#endif

#include <iostream>

int main()
{
    std::cout << &X::m << std::endl;
    std::cout << &X::f << std::endl;

    std::cout << &Y::m << std::endl;
    std::cout << &Y::f << std::endl;
    std::cout << &Y::g << std::endl;

    std::cout << &Z::h << std::endl;

#if !defined(_MSC_VER) || defined(__clang__)
    std::cout << &pair<int, float>::second << std::endl;
#endif
}

Sample output:

&X::m [int]
&X::f [int() const]
&Y::m [int]
&X::f [int() const]
&Y::g [int() const]
&Z::(unknown) [void()]

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 or later

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 - overrides mod_static and mod_function and returns all members regardless of kind

  • mod_inherited - includes members of base classes

  • mod_hidden - includes hidden inherited members

<boost/describe/​modifier_description.hpp>

The header modifier_description.hpp includes modifiers.hpp and invokes BOOST_DESCRIBE_ENUM on modifiers, allowing describe_enumerators<modifiers> to work.

<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.

Code Example 1. Using descriptor_by_name
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.

Code Example 2. Using descriptor_by_pointer
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.

This documentation is copyright 2020, 2021 Peter Dimov and is distributed under the Boost Software License, Version 1.0.