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.

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.

Providing enum_to_string in a library is made difficult by the fact that the desired behavior when the passed value does not correspond to a named enumerator varies depending on the specific use case. But, since defining the function is trivial when describe_enumerators is available, every user can easily have his own.

This specific example elects to return "(unnamed)" when the enum value doesn’t have a name.

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

string_to_enum

The opposite of the previous example; returns an enumerator value when given the enumerator name. The same problem exists here with respect to the error handling strategy when the string passed does not correspond to any enumerator name. This example 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;
    }
}

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

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

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.6 or later with -std=c++14 or above

  • Visual Studio 2015, 2017, 2019

Tested on Travis 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 = /*...*/;

} }

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.

<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/bases.hpp>

namespace boost {
namespace describe {

template<class T, unsigned M> using 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.

<boost/describe/members.hpp>

namespace boost {
namespace describe {

template<class T, unsigned M> using 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.

<boost/describe.hpp>

This convenience header includes all the headers previously described.

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