message: move enum-string conversion routines

Move enum conversion routines out of the 'details' namespace
so they are available for library users and consolidate them
in the 'native_types' header.

In order to support future work of template-specialization on
`variant` types, this also added a level of indirection in the
generated specializations for enums so that they are handled
through a struct-with-op pattern.  (Functions cannot be partially
specialized and overloading the template with std::variant support
requires that).

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I1db7cface90abe7296b9043ca9caa247368821de
diff --git a/include/sdbusplus/message/append.hpp b/include/sdbusplus/message/append.hpp
index 0fb1eb8..5dd8645 100644
--- a/include/sdbusplus/message/append.hpp
+++ b/include/sdbusplus/message/append.hpp
@@ -42,15 +42,6 @@
 namespace details
 {
 
-/** @brief Convert from a native type to a string.
- *
- *  Some C++ types cannot be represented directly on dbus, so we encode
- *  them as strings.  Enums are the primary example of this.  This is a
- *  template function prototype for the conversion to string functions.
- */
-template <typename T>
-std::string convert_to_string(T) = delete;
-
 /** @struct can_append_multiple
  *  @brief Utility to identify C++ types that may not be grouped into a
  *         single sd_bus_message_append call and instead need special
@@ -179,7 +170,7 @@
     static std::enable_if_t<std::is_same_v<S, Td<T>> && std::is_enum_v<Td<T>>>
         op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
     {
-        auto value = convert_to_string<Td<T>>(t);
+        auto value = sdbusplus::message::convert_to_string<Td<T>>(t);
         sdbusplus::message::append(intf, m, value);
     }
 };
diff --git a/include/sdbusplus/message/native_types.hpp b/include/sdbusplus/message/native_types.hpp
index d76604b..1f77914 100644
--- a/include/sdbusplus/message/native_types.hpp
+++ b/include/sdbusplus/message/native_types.hpp
@@ -175,6 +175,52 @@
 using signature = details::string_wrapper;
 using unix_fd = details::unix_fd_type;
 
+namespace details
+{
+
+template <typename T>
+struct convert_from_string
+{
+    static auto op(const std::string&) noexcept = delete;
+};
+
+template <typename T>
+struct convert_to_string
+{
+    static std::string op(T) = delete;
+};
+
+} // namespace details
+
+/** @brief Convert from a string to a native type.
+ *
+ *  Some C++ types cannot be represented directly on dbus, so we encode
+ *  them as strings.  Enums are the primary example of this.  This is a
+ *  template function prototype for the conversion from string functions.
+ *
+ *  @return A std::optional<T> containing the value if conversion is possible.
+ */
+template <typename T>
+auto convert_from_string(const std::string& str) noexcept
+{
+    return details::convert_from_string<T>::op(str);
+};
+
+/** @brief Convert from a native type to a string.
+ *
+ *  Some C++ types cannot be represented directly on dbus, so we encode
+ *  them as strings.  Enums are the primary example of this.  This is a
+ *  template function prototype for the conversion to string functions.
+ *
+ *  @return A std::string containing an encoding of the value, if conversion is
+ *          possible.
+ */
+template <typename T>
+std::string convert_to_string(T t)
+{
+    return details::convert_to_string<T>::op(t);
+}
+
 } // namespace message
 } // namespace sdbusplus
 
diff --git a/include/sdbusplus/message/read.hpp b/include/sdbusplus/message/read.hpp
index 92a2839..e64066e 100644
--- a/include/sdbusplus/message/read.hpp
+++ b/include/sdbusplus/message/read.hpp
@@ -43,15 +43,6 @@
 namespace details
 {
 
-/** @brief Convert from a string to a native type.
- *
- *  Some C++ types cannot be represented directly on dbus, so we encode
- *  them as strings.  Enums are the primary example of this.  This is a
- *  template function prototype for the conversion from string functions.
- */
-template <typename T>
-auto convert_from_string(const std::string&) noexcept = delete;
-
 /** @struct can_read_multiple
  *  @brief Utility to identify C++ types that may not be grouped into a
  *         single sd_bus_message_read call and instead need special
@@ -167,7 +158,7 @@
         std::string value{};
         sdbusplus::message::read(intf, m, value);
 
-        auto r = convert_from_string<Td<T>>(value);
+        auto r = sdbusplus::message::convert_from_string<Td<T>>(value);
         if (!r)
         {
             throw sdbusplus::exception::InvalidEnumString();
diff --git a/tools/sdbusplus/templates/interface.server.hpp.mako b/tools/sdbusplus/templates/interface.server.hpp.mako
index 1a3d25e..92f29ee 100644
--- a/tools/sdbusplus/templates/interface.server.hpp.mako
+++ b/tools/sdbusplus/templates/interface.server.hpp.mako
@@ -216,25 +216,26 @@
 } // namespace ${s}
     % endfor
 
-namespace message
-{
-namespace details
+namespace message::details
 {
     % for e in interface.enums:
 template <>
-inline auto convert_from_string<${interface.cppNamespace()}::${e.name}>(
-        const std::string& value) noexcept
+struct convert_from_string<${interface.cppNamespace()}::${e.name}>
 {
-    return ${interface.cppNamespace()}::convertStringTo${e.name}(value);
-}
+    static auto op(const std::string& value) noexcept
+    {
+        return ${interface.cppNamespace()}::convertStringTo${e.name}(value);
+    }
+};
 
 template <>
-inline std::string convert_to_string<${interface.cppNamespace()}::${e.name}>(
-        ${interface.cppNamespace()}::${e.name} value)
+struct convert_to_string<${interface.cppNamespace()}::${e.name}>
 {
-    return ${interface.cppNamespace()}::convert${e.name}ToString(value);
-}
+    static std::string op(${interface.cppNamespace()}::${e.name} value)
+    {
+        return ${interface.cppNamespace()}::convert${e.name}ToString(value);
+    }
+};
     % endfor
-} // namespace details
-} // namespace message
+} // namespace message::details
 } // namespace sdbusplus