From 889b9d44930717437609f2c438600bb294ffa274 Mon Sep 17 00:00:00 2001
From: Felix Weiglhofer <weiglhofer@fias.uni-frankfurt.de>
Date: Tue, 6 Jun 2023 12:34:09 +0000
Subject: [PATCH] algo: Rework SerializableEnum.h

- Renamed header to EnumDict.h
- Implementation detail are now hidden with `CBM_ENUM_DICT`, this also
  simplifies enum declarations
- Use std::vector underneath instead of std::set
---
 .clang-format                     |   2 +
 algo/base/RecoParams.h            |  31 +++-----
 algo/base/config/Yaml.h           |   7 +-
 algo/base/util/EnumDict.h         | 116 ++++++++++++++++++++++++++++++
 algo/base/util/SerializableEnum.h | 111 ----------------------------
 5 files changed, 131 insertions(+), 136 deletions(-)
 create mode 100644 algo/base/util/EnumDict.h
 delete mode 100644 algo/base/util/SerializableEnum.h

diff --git a/.clang-format b/.clang-format
index 4e17fee5b3..c7526acf06 100644
--- a/.clang-format
+++ b/.clang-format
@@ -142,6 +142,8 @@ SpacesInContainerLiterals: false
 SpacesInParentheses: false
 SpacesInSquareBrackets: false
 
+WhitespaceSensitiveMacros: ['CBM_ENUM_DICT']
+
 Standard: c++11
 
 TabWidth: 8
diff --git a/algo/base/RecoParams.h b/algo/base/RecoParams.h
index 184e78a1e5..b1f08da263 100644
--- a/algo/base/RecoParams.h
+++ b/algo/base/RecoParams.h
@@ -8,7 +8,7 @@
 
 #include "Prelude.h"
 #include "config/Property.h"
-#include "util/SerializableEnum.h"
+#include "util/EnumDict.h"
 
 namespace cbm::algo
 {
@@ -97,27 +97,16 @@ namespace cbm::algo
     static constexpr auto Properties = std::make_tuple(config::Property(&RecoParams::sts, "sts", "STS reco settings"));
   };
 
-  template<>
-  struct EnumIsSerializable<RecoParams::SortMode> : std::true_type {
-  };
-
-  template<>
-  inline const EnumDict_t<RecoParams::SortMode>& EnumDict<RecoParams::SortMode> = {
-    {"BlockSort", RecoParams::SortMode::BlockSort},
-    {"CUBSegmentedSort", RecoParams::SortMode::CUBSegmentedSort},
-  };
-
-  template<>
-  struct EnumIsSerializable<RecoParams::UnpackMode> : std::true_type {
-  };
-
-  template<>
-  inline const EnumDict_t<RecoParams::UnpackMode>& EnumDict<RecoParams::UnpackMode> = {
-    {"CPU", RecoParams::UnpackMode::CPU},
-    {"XPU", RecoParams::UnpackMode::XPU},
-  };
+};  // namespace cbm::algo
 
+CBM_ENUM_DICT(cbm::algo::RecoParams::SortMode,
+  {"BlockSort", RecoParams::SortMode::BlockSort},
+  {"CUBSegmentedSort", RecoParams::SortMode::CUBSegmentedSort}
+);
 
-};  // namespace cbm::algo
+CBM_ENUM_DICT(cbm::algo::RecoParams::UnpackMode,
+  {"CPU", RecoParams::UnpackMode::CPU},
+  {"XPU", RecoParams::UnpackMode::XPU}
+);
 
 #endif  // CBM_ALGO_BASE_RECOPARAMS_H
diff --git a/algo/base/config/Yaml.h b/algo/base/config/Yaml.h
index cbdd7a2f9a..cb836b0465 100644
--- a/algo/base/config/Yaml.h
+++ b/algo/base/config/Yaml.h
@@ -13,7 +13,7 @@
 #include "BaseTypes.h"
 #include "Prelude.h"
 #include "Property.h"
-#include "util/SerializableEnum.h"
+#include "util/EnumDict.h"
 
 namespace cbm::algo::config
 {
@@ -29,8 +29,7 @@ namespace cbm::algo::config
   {
     using Type = std::remove_cv_t<std::remove_reference_t<T>>;
 
-    static_assert(!IsEnum<T> || EnumIsSerializable<Type>::value, "Enum must be serializable");
-
+    static_assert(!IsEnum<T> || detail::EnumHasDict_v<T>, "Enum must have a dictionary to be deserializable");
 
     // TODO: error handling
     if constexpr (IsBaseType<Type>) { return node.as<Type>(); }
@@ -123,7 +122,7 @@ namespace cbm::algo::config
     template<typename T>
     void DoDump(const T& object, YAML::Emitter& ss, std::optional<YAML::EMITTER_MANIP> formatEntries = {})
     {
-      static_assert(!IsEnum<T> || EnumIsSerializable<T>::value, "Enum must be serializable");
+      static_assert(!IsEnum<T> || detail::EnumHasDict_v<T>, "Enum must have a dictionary");
 
       if constexpr (IsBaseType<T>) { ss << object; }
       else if constexpr (IsEnum<T>) {
diff --git a/algo/base/util/EnumDict.h b/algo/base/util/EnumDict.h
new file mode 100644
index 0000000000..6dc3341bcd
--- /dev/null
+++ b/algo/base/util/EnumDict.h
@@ -0,0 +1,116 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#ifndef CBM_ALGO_BASE_UTIL_SERIALIZABLEENUM_H
+#define CBM_ALGO_BASE_UTIL_SERIALIZABLEENUM_H
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <optional>
+#include <stdexcept>
+#include <string_view>
+#include <vector>
+
+#include <fmt/format.h>
+
+namespace cbm::algo
+{
+  namespace detail
+  {
+    template<typename T>
+    using EnumDict_t = std::vector<std::pair<std::string_view, T>>;
+
+    template<typename T>
+    inline const EnumDict_t<T> EnumDict;
+
+    template<typename T>
+    struct EnumHasDict : std::false_type {
+    };
+
+    template<typename T>
+    inline constexpr bool EnumHasDict_v = EnumHasDict<T>::value;
+  }  // namespace detail
+
+  template<typename T, typename = std::enable_if_t<detail::EnumHasDict_v<T>>>
+  std::optional<T> FromString(std::string_view str, bool caseSensitive = false)
+  {
+    const auto& dict = detail::EnumDict<T>;
+    auto it          = std::find_if(dict.begin(), dict.end(), [&](const auto& pair) {
+      if (caseSensitive) return pair.first == str;
+      else
+        return boost::iequals(pair.first, str);
+    });
+    if (it == dict.end()) return std::nullopt;
+    return it->second;
+  }
+
+  template<typename T, typename = std::enable_if_t<detail::EnumHasDict_v<T>>>
+  std::string_view ToString(T t)
+  {
+    const auto& dict = detail::EnumDict<T>;
+    auto it          = std::find_if(dict.begin(), dict.end(), [t](const auto& pair) { return pair.second == t; });
+    if (it == dict.end()) throw std::runtime_error(fmt::format("Entry {} for enum missing!", static_cast<int>(t)));
+    return it->first;
+  }
+}  // namespace cbm::algo
+
+/**
+   * @brief Convert enums to strings and back.
+   *
+   * @param type The enum type.
+   *
+   * Example:
+   * @code{.cpp}
+   * enum class Detector {
+   *  STS,
+   *  TOF,
+   * };
+   *
+   * CBM_ENUM_DICT(Detector,
+   *  {"sts", Detector::STS},
+   *  {"tof", Detector::TOF}
+   * );
+   *
+   * // Use it like this:
+   * L_(info) << ToString(Detector::STS); // Prints "sts"
+   *
+   * std::optional<Detector> d = FromString<Detector>("tof"); // *d == Detector::TOF
+   * std::optional<Detector> d2 = FromString<Detector>("invalid"); // d2 == std::nullopt
+   * @endcode
+   */
+#define CBM_ENUM_DICT(type, ...)                                                                                       \
+  template<>                                                                                                           \
+  inline const cbm::algo::detail::EnumDict_t<type> cbm::algo::detail::EnumDict<type> = {__VA_ARGS__};                  \
+  template<>                                                                                                           \
+  struct cbm::algo::detail::EnumHasDict<type> : std::true_type {                                                       \
+  }
+
+
+// Stream operators for enums
+// Placed in global namespace to be found by ADL e.g. for std::ostream_iterator
+namespace std
+{
+  template<typename T, typename = std::enable_if_t<cbm::algo::detail::EnumHasDict_v<T>>>
+  std::ostream& operator<<(std::ostream& os, T t)
+  {
+    os << cbm::algo::ToString(t);
+    return os;
+  }
+
+  template<typename T, typename = std::enable_if_t<cbm::algo::detail::EnumHasDict_v<T>>>
+  std::istream& operator>>(std::istream& is, T& t)
+  {
+    std::string str;
+    is >> str;
+    auto maybet = cbm::algo::FromString<T>(str);
+
+    if (!maybet) throw std::invalid_argument("Could not parse " + str + " as Enum");
+    t = *maybet;
+
+    return is;
+  }
+}  // namespace std
+
+#endif  //CBM_ALGO_BASE_UTIL_SERIALIZABLEENUM_H
diff --git a/algo/base/util/SerializableEnum.h b/algo/base/util/SerializableEnum.h
deleted file mode 100644
index 84ffd80482..0000000000
--- a/algo/base/util/SerializableEnum.h
+++ /dev/null
@@ -1,111 +0,0 @@
-/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Felix Weiglhofer [committer] */
-#ifndef CBM_ALGO_BASE_UTIL_SERIALIZABLEENUM_H
-#define CBM_ALGO_BASE_UTIL_SERIALIZABLEENUM_H
-
-#include <boost/algorithm/string/predicate.hpp>
-
-#include <algorithm>
-#include <iostream>
-#include <optional>
-#include <set>
-#include <stdexcept>
-#include <string_view>
-
-#include <fmt/format.h>
-
-namespace cbm::algo
-{
-  /**
-   * @brief Helper class to serialize enums to strings and back.
-   *
-   * @tparam T The enum type.
-   *
-   * To use this class, you need to specialize EnumIsSerializable<T> and EnumStringMap<T>.
-   * Used to indicate that an enum can be converted to / from a string.
-   *
-   * Example:
-   * @code{.cpp}
-   * enum class Detector {
-   *  STS,
-   *  TOF,
-   * };
-   *
-   * // Set to true to indicate that the enum is serializable.
-   * template<>
-   * struct EnumIsSerializable<Detector> : std::true_type {};
-   *
-   * // Create dictionary to convert enum to string and back.
-   * template<>
-   * const EnumDict_t<Detector> EnumDict<Detector> = {
-   *  {"sts", Detector::STS},
-   *  {"tof", Detector::TOF},
-   * };
-   *
-   * // Use it like this:
-   * L_(info) << ToString(Detector::STS); // Prints "sts"
-   *
-   * std::optional<Detector> d = FromString<Detector>("tof"); // *d == Detector::TOF
-   * std::optional<Detector> d2 = FromString<Detector>("invalid"); // d2 == std::nullopt
-   * @endcode
-   */
-  template<typename T>
-  struct EnumIsSerializable : std::false_type {
-  };
-
-  template<typename T>
-  using EnumDict_t = std::set<std::pair<std::string_view, T>>;
-
-  template<typename T>
-  inline const EnumDict_t<T> EnumDict;
-
-  template<typename T, typename = std::enable_if_t<EnumIsSerializable<T>::value>>
-  std::optional<T> FromString(std::string_view str, bool caseSensitive = false)
-  {
-    const auto& set = EnumDict<T>;
-    auto it         = std::find_if(set.begin(), set.end(), [&](const auto& pair) {
-      if (caseSensitive) return pair.first == str;
-      else
-        return boost::iequals(pair.first, str);
-    });
-    if (it == set.end()) return std::nullopt;
-    return it->second;
-  }
-
-  template<typename T, typename = std::enable_if_t<EnumIsSerializable<T>::value>>
-  std::string_view ToString(T t)
-  {
-    const auto& set = EnumDict<T>;
-    auto it         = std::find_if(set.begin(), set.end(), [t](const auto& pair) { return pair.second == t; });
-    if (it == set.end()) throw std::runtime_error(fmt::format("Entry {} for enum missing!", static_cast<int>(t)));
-    return it->first;
-  }
-}  // namespace cbm::algo
-
-// Stream operators for enums
-// Placed in global namespace to be found by ADL e.g. for std::ostream_iterator
-namespace std
-{
-  template<typename T, typename = std::enable_if_t<cbm::algo::EnumIsSerializable<T>::value>>
-  std::ostream& operator<<(std::ostream& os, T t)
-  {
-    os << cbm::algo::ToString(t);
-    return os;
-  }
-
-  template<typename T, typename = std::enable_if_t<cbm::algo::EnumIsSerializable<T>::value>>
-  std::istream& operator>>(std::istream& is, T& t)
-  {
-    std::string str;
-    is >> str;
-    auto maybet = cbm::algo::FromString<T>(str);
-
-    if (!maybet) throw std::invalid_argument("Could not parse " + str + " as Enum");
-    t = *maybet;
-
-    return is;
-  }
-}  // namespace std
-
-#endif  //CBM_ALGO_BASE_UTIL_SERIALIZABLEENUM_H
-- 
GitLab