1/* ----------------------------------------------------------------------------
2* Use, modification and distribution is subject to the Boost Software
3* License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
4* http://www.boost.org/LICENSE_1_0.txt)
5
6* See http://www.boost.org for updates, documentation, and revision history.
7
8* Functionality to serialize std::optional<T> to boost::archive
9* Inspired from this PR: https://github.com/boostorg/serialization/pull/163
10* ---------------------------------------------------------------------------- */
11#pragma once
12
13// Defined only if boost serialization is enabled
14#if GTSAM_ENABLE_BOOST_SERIALIZATION
15#include <optional>
16#include <boost/config.hpp>
17
18#include <boost/archive/detail/basic_iarchive.hpp>
19#include <boost/move/utility_core.hpp>
20
21#include <boost/serialization/item_version_type.hpp>
22#include <boost/serialization/split_free.hpp>
23#include <boost/serialization/level.hpp>
24#include <boost/serialization/nvp.hpp>
25#include <boost/serialization/version.hpp>
26#include <boost/type_traits/is_pointer.hpp>
27#include <boost/serialization/detail/stack_constructor.hpp>
28#include <boost/serialization/detail/is_default_constructible.hpp>
29
30/** A bunch of declarations to deal with gcc bug
31 * The compiler has a difficult time distinguisihing between:
32 *
33 * template<template <Archive, class U> class SPT> void load(Archive, SPT<U>&, const unsigned int) : <boost/serialization/shared_ptr.hpp>
34 *
35 * and
36 *
37 * template<T> void load(Archive, std::optional<T>&, const unsigned int) : <std_optional_serialization.h>
38 *
39 * The compiler will try to instantiate an object of the type of std::optional<boost::serialization::U> which is not valid since U is not a type and
40 * thus leading to a whole series of errros.
41 *
42 * This is a well known bug in gcc documented here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84075
43 * A minimal reproducible example here: https://godbolt.org/z/anj9YjnPY
44 *
45 * For std::optional the workaround is just provide the traits needed to make std::optional<boost::serialization::U> possible
46 * This is not an issue since there is no actual type boost::serialization::U and we are not falsely providing a specialization
47 * for std::optional<boost::serialization::U>
48 */
49#ifdef __GNUC__
50#if __GNUC__ >= 7 && __cplusplus >= 201703L
51// Based on https://github.com/borglab/gtsam/issues/1738, we define U as a complete type.
52namespace boost { namespace serialization { struct U{}; } }
53namespace std { template<> struct is_trivially_default_constructible<boost::serialization::U> : std::false_type {}; }
54namespace std { template<> struct is_trivially_copy_constructible<boost::serialization::U> : std::false_type {}; }
55namespace std { template<> struct is_trivially_move_constructible<boost::serialization::U> : std::false_type {}; }
56// QCC (The QNX GCC-based Compiler) also has this issue, but it also extends to trivial destructor.
57#if defined(__QNX__)
58namespace std { template<> struct is_trivially_destructible<boost::serialization::U> : std::false_type {}; }
59#endif
60#endif
61#endif
62
63/*
64 * PR https://github.com/boostorg/serialization/pull/163 was merged
65 * on September 3rd 2023,
66 * and so the below code is now a part of Boost 1.84.
67 * We include it for posterity, hence the check for BOOST_VERSION being less
68 * than 1.84.
69 */
70#if BOOST_VERSION < 108400
71// function specializations must be defined in the appropriate
72// namespace - boost::serialization
73namespace boost {
74namespace serialization {
75
76template <class Archive, class T>
77void save(Archive& ar, const std::optional<T>& t, const unsigned int /*version*/
78) {
79 // It is an inherent limitation to the serialization of optional.hpp
80 // that the underlying type must be either a pointer or must have a
81 // default constructor.
82 static_assert(boost::serialization::detail::is_default_constructible<T>::value || boost::is_pointer<T>::value);
83 const bool tflag = t.has_value();
84 ar << boost::serialization::make_nvp(n: "initialized", v: tflag);
85 if (tflag) {
86 ar << boost::serialization::make_nvp("value", *t);
87 }
88}
89
90template <class Archive, class T>
91void load(Archive& ar, std::optional<T>& t, const unsigned int /*version*/) {
92 bool tflag;
93 ar >> boost::serialization::make_nvp(n: "initialized", v&: tflag);
94 if (!tflag) {
95 t.reset();
96 return;
97 }
98
99 if (!t.has_value()) {
100 // Need to be default constructible
101 t.emplace();
102 }
103 ar >> boost::serialization::make_nvp("value", *t);
104}
105
106template <class Archive, class T>
107void serialize(Archive& ar, std::optional<T>& t, const unsigned int version) {
108 boost::serialization::split_free(ar, t, version);
109}
110
111} // namespace serialization
112} // namespace boost
113#endif // BOOST_VERSION < 108400
114#endif // GTSAM_ENABLE_BOOST_SERIALIZATION
115