eyalz800 / serializer

A single header standard C++ serialization framework.
MIT License
189 stars 24 forks source link

Support C++17's std::variant and std::optional. #3

Closed MatchPointDarko closed 5 years ago

MatchPointDarko commented 5 years ago

I propose a serialization implementation for std::variant and std::optional here.

eyalz800 commented 5 years ago

@MatchPointDarko I didn't forget about it, I just got a little busy with another project, I plan to check the variant recursive vs O(1) implementation and merge when done.

MatchPointDarko commented 5 years ago

Not needed anymore.. i see this project is abandoned.. i'm using mike-serializer: https://github.com/MatchPointDarko/serializer

eyalz800 commented 5 years ago

@MatchPointDarko Here is my take on similar change, with no recursions:

/**
 * Serialize std::optional, operates on loading (input) archives.
 */
template <typename Archive,
          typename Type,
          typename...,
          typename = typename Archive::loading>
void serialize(Archive & archive, std::optional<Type> & optional)
{
    // Load whether has value.
    bool has_value{};
    archive(has_value);

    // If does not have a value.
    if (!has_value) {
        optional = std::nullopt;
        return;
    }

    // If the type is default constructible.
    if constexpr (std::is_default_constructible_v<Type>) {
        // Create the value if does not exist.
        if (!optional) {
            optional = Type{};
        }

        // Load the value.
        archive(*optional);
    } else {
        // The object storage.
        std::aligned_storage_t<sizeof(Type), alignof(Type)> storage;

        // Create the object at the storage.
        std::unique_ptr<Type, void (*)(Type *)> object(
            access::placement_new<Type>(std::addressof(storage)),
            [](auto pointer) { access::destruct(*pointer); });

        // Load the object.
        archive(*object);

        // Assign the loaded object.
        optional = std::move(*object);
    }
}

/**
 * Serialize std::optional, operates on saving (output) archives.
 */
template <typename Archive,
          typename Type,
          typename...,
          typename = typename Archive::saving>
void serialize(Archive & archive, const std::optional<Type> & optional)
{
    // Save has value.
    bool has_value = optional.has_value();

    // If has value, save it.
    if (has_value) {
        archive(has_value, *optional);
    } else {
        archive(has_value);
    }
}

/**
 * Serialize std::variant, operates on loading (input) archives.
 */
template <typename Archive,
          typename... Types,
          typename = typename Archive::loading>
void serialize(Archive & archive, std::variant<Types...> & variant)
{
    // Test for maximum number of types.
    static_assert(sizeof...(Types) < 0xff, "Max variant types reached.");

    // The variant index.
    unsigned char index{};

    // Load the index.
    archive(index);

    // Check that loaded index is inside bounds.
    if (index >= sizeof...(Types)) {
        throw variant_index_out_of_range("Variant index out of range");
    }

    // The variant type.
    using variant_type = std::variant<Types...>;

    // Loader type.
    using loader_type =
        void (*)(Archive & archive, variant_type & variant);

    // Loaders per variant index.
    static constexpr loader_type loaders[] = {[](auto & archive,
                                                 auto & variant) {
        // If the type is default constructible.
        if constexpr (std::is_default_constructible_v<Types>) {
            // If does not have the needed type, assign it.
            if (!std::get_if<Types>(&variant)) {
                variant = Types{};
            }

            // Load the value.
            archive(*std::get_if<Types>(&variant));
        } else {
            // The object storage.
            std::aligned_storage_t<sizeof(Types), alignof(Types)> storage;

            // Create the object at the storage.
            std::unique_ptr<Types, void (*)(Types *)> object(
                access::placement_new<Types>(std::addressof(storage)),
                [](auto pointer) { access::destruct(*pointer); });

            // Load the object.
            archive(*object);

            // Assign the loaded object.
            variant = std::move(*object);
        }
    }...};

    // Execute the appropriate loader.
    loaders[index](archive, variant);
}

/**
 * Serialize std::variant, operates on saving (output) archives.
 */
template <typename Archive,
          typename... Types,
          typename = typename Archive::saving>
void serialize(Archive & archive, const std::variant<Types...> & variant)
{
    // Test for maximum number of types.
    static_assert(sizeof...(Types) < 0xff, "Max variant types reached.");

    // The variant index.
    auto variant_index = variant.index();

    // Disallow serializations of valueless variant.
    if (std::variant_npos == variant_index) {
        throw attempt_to_serialize_valueless_variant(
            "Cannot serialize a valueless variant.");
    }

    // The index to save.
    auto index = static_cast<unsigned char>(variant_index & 0xff);

    // Save the variant object.
    std::visit(
        [index, &archive](auto & object) { archive(index, object); },
        variant);
}