From 06696d0ac362b0738c2a0508f67b4b2ea234eaa1 Mon Sep 17 00:00:00 2001 From: Shane Peelar Date: Mon, 19 Oct 2020 22:31:44 -0400 Subject: [PATCH] C++17: use inline globals for StaticObjects This prevents multiple definition errors in Clang, and also stops dllexporting functions with internal linkage. Degrades gracefully when C++17 is not present. Fix #595 Fix #652 Fix #582 Fix #643 --- include/cereal/archives/json.hpp | 2 +- include/cereal/cereal.hpp | 22 ++++++++++++ include/cereal/details/polymorphic_impl.hpp | 37 ++++++++++++++++++--- include/cereal/macros.hpp | 4 ++- include/cereal/types/polymorphic.hpp | 2 +- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/include/cereal/archives/json.hpp b/include/cereal/archives/json.hpp index 80f11aa50..8fbae3072 100644 --- a/include/cereal/archives/json.hpp +++ b/include/cereal/archives/json.hpp @@ -286,7 +286,7 @@ namespace cereal void saveLong(T lu){ saveValue( static_cast( lu ) ); } public: -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER < 1916 //! MSVC only long overload to current node void saveValue( unsigned long lu ){ saveLong( lu ); }; #else // _MSC_VER diff --git a/include/cereal/cereal.hpp b/include/cereal/cereal.hpp index 99bed9d64..3cfa98f3d 100644 --- a/include/cereal/cereal.hpp +++ b/include/cereal/cereal.hpp @@ -258,6 +258,26 @@ namespace cereal Interfaces for other forms of serialization functions is similar. This macro should be placed at global scope. @ingroup Utility */ + + //! On C++17, define the StaticObject as inline to merge the definitions across TUs + //! This prevents multiple definition errors when this macro appears in a header file + //! included in multiple TUs. + #ifdef CEREAL_HAS_CPP17 + #define CEREAL_CLASS_VERSION(TYPE, VERSION_NUMBER) \ + namespace cereal { namespace detail { \ + template <> struct Version \ + { \ + static std::uint32_t registerVersion() \ + { \ + ::cereal::detail::StaticObject::getInstance().mapping.emplace( \ + std::type_index(typeid(TYPE)).hash_code(), VERSION_NUMBER ); \ + return VERSION_NUMBER; \ + } \ + static inline const std::uint32_t version = registerVersion(); \ + CEREAL_UNUSED_FUNCTION \ + }; /* end Version */ \ + } } // end namespaces + #else #define CEREAL_CLASS_VERSION(TYPE, VERSION_NUMBER) \ namespace cereal { namespace detail { \ template <> struct Version \ @@ -275,6 +295,8 @@ namespace cereal Version::registerVersion(); \ } } // end namespaces + #endif + // ###################################################################### //! The base output archive class /*! This is the base output archive for all output archives. If you create diff --git a/include/cereal/details/polymorphic_impl.hpp b/include/cereal/details/polymorphic_impl.hpp index 881394f98..c9a586dd3 100644 --- a/include/cereal/details/polymorphic_impl.hpp +++ b/include/cereal/details/polymorphic_impl.hpp @@ -69,12 +69,26 @@ have been registered with CEREAL_REGISTER_ARCHIVE. This must be called after all archives are registered (usually after the archives themselves have been included). */ +#ifdef CEREAL_HAS_CPP17 #define CEREAL_BIND_TO_ARCHIVES(...) \ namespace cereal { \ namespace detail { \ template<> \ struct init_binding<__VA_ARGS__> { \ - static bind_to_archives<__VA_ARGS__> const & b; \ + static inline bind_to_archives<__VA_ARGS__> const & b= \ + ::cereal::detail::StaticObject< \ + bind_to_archives<__VA_ARGS__> \ + >::getInstance().bind(); \ + CEREAL_BIND_TO_ARCHIVES_UNUSED_FUNCTION \ + }; \ + }} /* end namespaces */ +#else +#define CEREAL_BIND_TO_ARCHIVES(...) \ + namespace cereal { \ + namespace detail { \ + template<> \ + struct init_binding<__VA_ARGS__> { \ + static bind_to_archives<__VA_ARGS__> const& b; \ CEREAL_BIND_TO_ARCHIVES_UNUSED_FUNCTION \ }; \ bind_to_archives<__VA_ARGS__> const & init_binding<__VA_ARGS__>::b = \ @@ -82,6 +96,7 @@ bind_to_archives<__VA_ARGS__> \ >::getInstance().bind(); \ }} /* end namespaces */ +#endif namespace cereal { @@ -654,7 +669,7 @@ namespace cereal auto ptr = PolymorphicCasters::template downcast( dptr, baseInfo ); - #if defined(_MSC_VER) && !defined(__clang__) + #if defined(_MSC_VER) && _MSC_VER < 1916 && !defined(__clang__) savePolymorphicSharedPtr( ar, ptr, ::cereal::traits::has_shared_from_this::type() ); // MSVC doesn't like typename here #else // not _MSC_VER savePolymorphicSharedPtr( ar, ptr, typename ::cereal::traits::has_shared_from_this::type() ); @@ -680,9 +695,23 @@ namespace cereal //! of instantiate_polymorphic_binding struct adl_tag {}; - //! Tag for init_binding, bind_to_archives and instantiate_polymorphic_binding. Due to the use of anonymous - //! namespace it becomes a different type in each translation unit. + //! Tag for init_binding, bind_to_archives and instantiate_polymorphic_binding. + //! For C++14 and below, we must instantiate a unique StaticObject per TU that is + //! otherwise identical -- otherwise we get multiple definition problems (ODR violations). + //! To achieve this, put a tag in an anonymous namespace and use it as a template argument. + //! + //! For C++17, we can use static inline global variables to unify these definitions across + //! all TUs in the same shared object (DLL). The tag is therefore not necessary. + //! For convenience, keep it to not complicate other code, but don't put it in + //! an anonymous namespace. Now the template instantiations will correspond + //! to the same type, and since they are marked inline with C++17, they will be merged + //! across all TUs. +#ifdef CEREAL_HAS_CPP17 + struct polymorphic_binding_tag {}; +#else namespace { struct polymorphic_binding_tag {}; } +#endif + //! Causes the static object bindings between an archive type and a serializable type T template diff --git a/include/cereal/macros.hpp b/include/cereal/macros.hpp index ce2feb148..f2ef96c71 100644 --- a/include/cereal/macros.hpp +++ b/include/cereal/macros.hpp @@ -134,7 +134,9 @@ // ###################################################################### //! Checks if C++17 is available -#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +//! NOTE: clang v5 has a bug with inline variables, so disable C++17 on that compiler +#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) \ + && (!defined(__clang__) || __clang_major__ > 5) #define CEREAL_HAS_CPP17 #endif diff --git a/include/cereal/types/polymorphic.hpp b/include/cereal/types/polymorphic.hpp index f3db78f6c..957ecf14a 100644 --- a/include/cereal/types/polymorphic.hpp +++ b/include/cereal/types/polymorphic.hpp @@ -38,7 +38,7 @@ #include "cereal/details/traits.hpp" #include "cereal/details/polymorphic_impl.hpp" -#ifdef _MSC_VER +#if defined(_MSC_VER) && _MSC_VER < 1916 #define CEREAL_STATIC_CONSTEXPR static #else #define CEREAL_STATIC_CONSTEXPR static constexpr