User-defined type support

There are various customization points to integrate user-defined types in this library.

Serialization traits

The first step to integrating a custom type is by providing which bencode data type it encodes to by specializing template <typename T> serialization_traits for your type.

Serialization traits has a single member type of type bencode_type that defines what bencode data type this type serializes to.

To make specialization of serialization_traits easy a few helpers are provided.

Helper classes:
  • serializes_to_runtime_type

  • serializes_to_integer

  • serializes_to_integer

  • serializes_to_string

  • serializes_to_list

  • template <dict_key_order Order = dict_key_order::sorted> struct serializes_to_dict

Helper macros:
  • BENCODE_SERIALIZES_TO_RUNTIME_TYPE

  • BENCODE_SERIALIZES_TO_INTEGER

  • BENCODE_SERIALIZES_TO_STRING

  • BENCODE_SERIALIZES_TO_LIST

  • BENCODE_SERIALIZES_TO_DICT_SORTED

  • BENCODE_SERIALIZES_TO_DICT_UNSORTED

When the user-defined type can be converted to different bencode data types depending on the value serializes_to_runtime_type or BENCODE_SERIALIZES_TO_RUNTIME_TYPE should be used.

When a type serializes to a dict we make a differentiation between sorted and unsorted dicts. Since a bencode dict requires keys to be in sorted order we must mark map-like types with unsorted keys as such.

To define a type that behaves like a pointer (eg. smart pointers), the static member variable is_pointer must be set to true. This will make sure that when serializing/deserializing that type the value is dereferenced when needed.

For types that behave like standard library types, specializing template <typename T> serialization_traits can be enough to enable full support. The bencode library will try to find an implementation that works for given type. If no suitable build-in methods exist, additional customization points must be implemented.

After specializing serialization_traits the user-defined type satisfies the serializable concept.

Example:

struct rgb_color
{
    std::uint8_t r, g, g;
};

// Specialization with a macro.
namespace bencode {
BENCODE_SERIALIZES_TO_LIST(rgb_color)
}

// Equivalent specialization without macro use.
namespace bencode {
template <> struct serialization_traits<rgb_color> : serializes_to_list {};
}
template <typename T>
class my_smart_pointer : {...}

namespace bencode {
template <typename T>
struct serialization_traits<my_smart_pointer<T>>
        : serializes_to<serialization_traits<T>::type>
{
    static constexpr bool is_pointer = true;
};
}

Event producer

The second required customization point to enable support for a user-defined type is the bencode_connect().

template <event_consumer EC>
constexpr void bencode_connect(
        customization_point_type<rgb_color>, EC& consumer, const rgb_color& value)
{
    consumer.list_begin();
    consumer.integer(value.r);
    consumer.list_item();
    consumer.integer(value.g);
    consumer.list_item();
    consumer.integer(value.b);
    consumer.list_item();
    consumer.list_end()
}

After overriding this function the type satisfies the event_producer concept.

After satisfying serializable and cpp:concept:event_producer the user defined type can be serialized with encoder and assigned to bvalue.

Important

All customization points prefixed with bencode_ must be defined in the namespace of the type for which you want to enable a library feature. These functions use Argument-dependent lookup (ADL) to identify the correct overload.

Assignment to bvalue

Types that satisfy event_producer have a default implementation that allows the type to be assigned to bvalue, but is not always the most efficient. The default can be overriden by overriding bencode_assign_to_bvalue()

template <typename Policy>
constexpr auto bencode_assign_to_bvalue(
        customization_point_type<rgb_color>, basic_bvalue<Policy>& bv, const rgb_color& value)
{
    auto& l = bv.emplace_list();
    l.push_back(value.r);
    l.push_back(value.g);
    l.push_back(value.b);
}

Direct comparison to bvalue

The content of a bvalue can be compared with that of a custom type without creating a temporary bvalue object. This is done be overriding bencode_compare_equality_with_bvalue()

template <typename Policy>
bencode_compare_equality_with_bvalue(
        customization_point_type<rgb_color>, basic_bvalue<Policy>& bv, const rgb_color& value)
{
    if (!is_list(bv)) return false;
    if (bv.size() != 3) return false;
    return (bv[0] == value.r) && (bv[1] == value.g) && (b[2] == value.b);
}

For types that can be ordered bencode_compare_three_way_with_bvalue() can be overridden.

template <typename Policy>
std::partial_ordering bencode_compare_three_way_with_bvalue(
        customization_point_type<rgb_color>, basic_bvalue<Policy>& bv, const rgb_color& value)
{
    if (!is_list(bv))  return std::partial_ordering::unordered;
    if (bv.size() < 3) return std::partial_ordering::greater;
    if (bv.size() > 3) return std::partial_ordering::less;

    auto first_ordering = (bv[0] <=> value.r);
    if (first_ordering == std::partial_ordering::equivalent) {
        auto second_ordering = (bv[1] <=> value.g);
        if (second_ordering == std::partial_ordering::equivalent) {
            return b[2] <=> value.b;
        } else {
            return second_ordering;
        }
    }
    return first_ordering
}

Conversion from bvalue to custom type

You can retrieve your custom type directly from a :cpp:clas::bvalue by implementing the bencode_convert_from_bvalue() customization point. This will allow the use of get_as with your type. Errors are reported with nonstd::expected.

template <typename Policy>
nonstd::expected<rgb_color, conversion_errc>
bencode_convert_from_bvalue(customization_point_type<rgb_color>, const basic_bvalue<Policy>& bv)
{
    if (!is_list(bv))
        return nonstd::make_unexpected(conversion_errc::not_list_type);

    const auto& l = get_list(bv)

    if (l.size() != 3)
        return nonstd::make_unexpected(conversion_errc::size_mismatch);

    return rgb_color {.r = l[0], .g = l[1], .b = l[2]};
}

Direct comparison to bview

Analogue with comparison with bvalue there are two comparison customization points for bview:

  • bencode_compare_equality_with_bview()

  • bencode_compare_three_way_with_bview()

The implementation for our example user-defined class is exactly the same as for the implementation for bvalue, except the function signature.

constexpr bool bencode_compare_equality_with_bview(
    customization_point_type<rgb_color>, const bview& bv, rgb_color value);

constexpr bool bencode_compare_three_way_with_bview(
    customization_point_type<rgb_color>, const bview& bv, rgb_color value);

Conversion from bview to custom type

Similar to conversion from bvalue there is a conversion from bview by implementing the bencode_convert_from_bview() customization point.

The implementation for our example user-defined class is exactly the same as for the implementation for bvalue, except the function signature.

nonstd::expected<rgb_color, conversion_errc>
bencode_convert_from_bview(customization_for<rgb_color>, const bview& bv);