Allow reading and appending of more complex types

This commit makes sdbusplus compatible with most containers that meet
a few requirements.  This includes:
std::unordered_map
std::array
std::set
boost::flat_set
boost::flat_map

Read requires a container to support emplace or emplace_back methods.
Append requires a container to suport a const iterator

Tested: The top level OpenBMC compiles properly, and the sdbusplus
unit tests compile and pass, and unit tests have been updated with a
few new types to ensure we see any breakages.

Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Change-Id: I5eb1cf7dc07bacc7aca62d87844794223ad4de80
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/Makefile.am b/Makefile.am
index 037640c..8c96b3e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,7 @@
 	sdbusplus/server/manager.hpp \
 	sdbusplus/server/object.hpp \
 	sdbusplus/slot.hpp \
+	sdbusplus/utility/container_traits.hpp \
 	sdbusplus/utility/tuple_to_array.hpp \
 	sdbusplus/utility/type_traits.hpp \
 	sdbusplus/vtable.hpp
diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp
index 85bb602..e81f813 100644
--- a/sdbusplus/message/append.hpp
+++ b/sdbusplus/message/append.hpp
@@ -1,10 +1,12 @@
 #pragma once
 
 #include <tuple>
-#include <sdbusplus/message/types.hpp>
-#include <sdbusplus/utility/type_traits.hpp>
-#include <sdbusplus/utility/tuple_to_array.hpp>
+
 #include <systemd/sd-bus.h>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/utility/container_traits.hpp>
+#include <sdbusplus/utility/tuple_to_array.hpp>
+#include <sdbusplus/utility/type_traits.hpp>
 
 namespace sdbusplus
 {
@@ -44,7 +46,8 @@
  *  User-defined types are expected to inherit from std::false_type.
  *
  */
-template <typename T> struct can_append_multiple : std::true_type
+template <typename T, typename Enable = void>
+struct can_append_multiple : std::true_type
 {
 };
 // std::string needs a c_str() call.
@@ -63,9 +66,11 @@
 template <> struct can_append_multiple<bool> : std::false_type
 {
 };
-// std::vector needs a loop.
+// std::vector/map/unordered_map/set need loops
 template <typename T>
-struct can_append_multiple<std::vector<T>> : std::false_type
+struct can_append_multiple<
+    T, typename std::enable_if<utility::has_const_iterator<T>::value>::type>
+    : std::false_type
 {
 };
 // std::pair needs to be broken down into components.
@@ -73,11 +78,6 @@
 struct can_append_multiple<std::pair<T1, T2>> : std::false_type
 {
 };
-// std::map needs a loop.
-template <typename T1, typename T2>
-struct can_append_multiple<std::map<T1, T2>> : std::false_type
-{
-};
 // std::tuple needs to be broken down into components.
 template <typename... Args>
 struct can_append_multiple<std::tuple<Args...>> : std::false_type
@@ -97,7 +97,7 @@
  *
  *  @tparam S - Type of element to append.
  */
-template <typename S> struct append_single
+template <typename S, typename Enable = void> struct append_single
 {
     // Downcast
     template <typename T> using Td = types::details::type_id_downcast_t<T>;
@@ -186,14 +186,17 @@
     }
 };
 
-/** @brief Specialization of append_single for std::vectors. */
-template <typename T> struct append_single<std::vector<T>>
+/** @brief Specialization of append_single for containers (ie vector, array,
+ * set, map, ect) */
+template <typename T>
+struct append_single<T, std::enable_if_t<utility::has_const_iterator<T>::value>>
 {
     template <typename S> static void op(sd_bus_message* m, S&& s)
     {
         constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
 
-        sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, dbusType.data());
+        sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
+                                      dbusType.data() + 1);
         for (auto& i : s)
         {
             sdbusplus::message::append(m, i);
@@ -217,23 +220,6 @@
     }
 };
 
-/** @brief Specialization of append_single for std::maps. */
-template <typename T1, typename T2> struct append_single<std::map<T1, T2>>
-{
-    template <typename S> static void op(sd_bus_message* m, S&& s)
-    {
-        constexpr auto dbusType = utility::tuple_to_array(
-            types::type_id<typename std::map<T1, T2>::value_type>());
-
-        sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, dbusType.data());
-        for (auto& i : s)
-        {
-            sdbusplus::message::append(m, i);
-        }
-        sd_bus_message_close_container(m);
-    }
-};
-
 /** @brief Specialization of append_single for std::tuples. */
 template <typename... Args> struct append_single<std::tuple<Args...>>
 {
diff --git a/sdbusplus/message/read.hpp b/sdbusplus/message/read.hpp
index df4ebc6..9f28c48 100644
--- a/sdbusplus/message/read.hpp
+++ b/sdbusplus/message/read.hpp
@@ -44,7 +44,8 @@
  *  User-defined types are expected to inherit from std::false_type.
  *
  */
-template <typename T> struct can_read_multiple : std::true_type
+template <typename T, typename Enable = void>
+struct can_read_multiple : std::true_type
 {
 };
 // std::string needs a char* conversion.
@@ -63,20 +64,23 @@
 template <> struct can_read_multiple<bool> : std::false_type
 {
 };
-// std::vector needs a loop.
-template <typename T> struct can_read_multiple<std::vector<T>> : std::false_type
+
+// std::vector/map/unordered_vector/set need loops
+template <typename T>
+struct can_read_multiple<
+    T,
+    typename std::enable_if<utility::has_emplace_method<T>::value ||
+                            utility::has_emplace_back_method<T>::value>::type>
+    : std::false_type
 {
 };
+
 // std::pair needs to be broken down into components.
 template <typename T1, typename T2>
 struct can_read_multiple<std::pair<T1, T2>> : std::false_type
 {
 };
-// std::map needs a loop.
-template <typename T1, typename T2>
-struct can_read_multiple<std::map<T1, T2>> : std::false_type
-{
-};
+
 // std::tuple needs to be broken down into components.
 template <typename... Args>
 struct can_read_multiple<std::tuple<Args...>> : std::false_type
@@ -96,7 +100,7 @@
  *
  *  @tparam S - Type of element to read.
  */
-template <typename S> struct read_single
+template <typename S, typename Enable = void> struct read_single
 {
     // Downcast
     template <typename T> using Td = types::details::type_id_downcast_t<T>;
@@ -169,20 +173,42 @@
 };
 
 /** @brief Specialization of read_single for std::vectors. */
-template <typename T> struct read_single<std::vector<T>>
+template <typename T>
+struct read_single<T,
+                   std::enable_if_t<utility::has_emplace_back_method<T>::value>>
 {
     template <typename S> static void op(sd_bus_message* m, S&& s)
     {
-        s.clear();
-
         constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
-        sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data());
+        sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+                                       dbusType.data() + 1);
 
         while (!sd_bus_message_at_end(m, false))
         {
-            std::remove_const_t<T> t{};
+            types::details::type_id_downcast_t<typename T::value_type> t;
             sdbusplus::message::read(m, t);
-            s.push_back(std::move(t));
+            s.emplace_back(std::move(t));
+        }
+
+        sd_bus_message_exit_container(m);
+    }
+};
+
+/** @brief Specialization of read_single for std::map. */
+template <typename T>
+struct read_single<T, std::enable_if_t<utility::has_emplace_method<T>::value>>
+{
+    template <typename S> static void op(sd_bus_message* m, S&& s)
+    {
+        constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
+        sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+                                       dbusType.data() + 1);
+
+        while (!sd_bus_message_at_end(m, false))
+        {
+            types::details::type_id_downcast_t<typename T::value_type> t;
+            sdbusplus::message::read(m, t);
+            s.emplace(std::move(t));
         }
 
         sd_bus_message_exit_container(m);
@@ -204,29 +230,6 @@
     }
 };
 
-/** @brief Specialization of read_single for std::maps. */
-template <typename T1, typename T2> struct read_single<std::map<T1, T2>>
-{
-    template <typename S> static void op(sd_bus_message* m, S&& s)
-    {
-        s.clear();
-
-        constexpr auto dbusType = utility::tuple_to_array(
-            types::type_id<typename std::map<T1, T2>::value_type>());
-
-        sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data());
-
-        while (!sd_bus_message_at_end(m, false))
-        {
-            std::pair<std::remove_const_t<T1>, std::remove_const_t<T2>> p{};
-            sdbusplus::message::read(m, p);
-            s.insert(std::move(p));
-        }
-
-        sd_bus_message_exit_container(m);
-    }
-};
-
 /** @brief Specialization of read_single for std::tuples. */
 template <typename... Args> struct read_single<std::tuple<Args...>>
 {
diff --git a/sdbusplus/message/types.hpp b/sdbusplus/message/types.hpp
index 6a30dd6..5d3be09 100644
--- a/sdbusplus/message/types.hpp
+++ b/sdbusplus/message/types.hpp
@@ -7,6 +7,7 @@
 #include <mapbox/variant.hpp>
 #include <systemd/sd-bus.h>
 
+#include <sdbusplus/utility/container_traits.hpp>
 #include <sdbusplus/utility/type_traits.hpp>
 #include <sdbusplus/message/native_types.hpp>
 
@@ -48,6 +49,35 @@
 namespace details
 {
 
+/** @brief Downcast type submembers.
+ *
+ * This allows std::tuple and std::pair members to be downcast to their
+ * non-const, nonref versions of themselves to limit duplication in template
+ * specializations
+ *
+ *  1. Remove references.
+ *  2. Remove 'const' and 'volatile'.
+ *  3. Convert 'char[N]' to 'char*'.
+ */
+template <typename T> struct downcast_members
+{
+    using type = T;
+};
+template <typename... Args> struct downcast_members<std::pair<Args...>>
+{
+    using type = std::pair<utility::array_to_ptr_t<
+        char, std::remove_cv_t<std::remove_reference_t<Args>>>...>;
+};
+
+template <typename... Args> struct downcast_members<std::tuple<Args...>>
+{
+    using type = std::tuple<utility::array_to_ptr_t<
+        char, std::remove_cv_t<std::remove_reference_t<Args>>>...>;
+};
+
+template <typename T>
+using downcast_members_t = typename downcast_members<T>::type;
+
 /** @brief Convert some C++ types to others for 'type_id' conversion purposes.
  *
  *  Similar C++ types have the same dbus type-id, so 'downcast' those to limit
@@ -59,8 +89,8 @@
  */
 template <typename T> struct type_id_downcast
 {
-    using type = typename utility::array_to_ptr_t<
-        char, std::remove_cv_t<std::remove_reference_t<T>>>;
+    using type = utility::array_to_ptr_t<
+        char, downcast_members_t<std::remove_cv_t<std::remove_reference_t<T>>>>;
 };
 
 template <typename T>
@@ -127,7 +157,8 @@
  *  Struct must have a 'value' tuple containing the dbus type.  The default
  *  value is an empty tuple, which is used to indicate an unsupported type.
  */
-template <typename T> struct type_id : public undefined_type_id
+template <typename T, typename Enable = void>
+struct type_id : public undefined_type_id
 {
 };
 // Specializations for built-in types.
@@ -176,11 +207,13 @@
 {
 };
 
-template <typename T> struct type_id<std::vector<T>>
+template <typename T>
+struct type_id<T, std::enable_if_t<utility::has_const_iterator<T>::value>>
+    : std::false_type
 {
-    static constexpr auto value =
-        std::tuple_cat(tuple_type_id<SD_BUS_TYPE_ARRAY>::value,
-                       type_id<type_id_downcast_t<T>>::value);
+    static constexpr auto value = std::tuple_cat(
+        tuple_type_id<SD_BUS_TYPE_ARRAY>::value,
+        type_id<type_id_downcast_t<typename T::value_type>>::value);
 };
 
 template <typename T1, typename T2> struct type_id<std::pair<T1, T2>>
@@ -192,13 +225,6 @@
                        tuple_type_id<SD_BUS_TYPE_DICT_ENTRY_END>::value);
 };
 
-template <typename T1, typename T2> struct type_id<std::map<T1, T2>>
-{
-    static constexpr auto value =
-        std::tuple_cat(tuple_type_id<SD_BUS_TYPE_ARRAY>::value,
-                       type_id<typename std::map<T1, T2>::value_type>::value);
-};
-
 template <typename... Args> struct type_id<std::tuple<Args...>>
 {
     static constexpr auto value =
diff --git a/sdbusplus/utility/container_traits.hpp b/sdbusplus/utility/container_traits.hpp
new file mode 100644
index 0000000..e778d17
--- /dev/null
+++ b/sdbusplus/utility/container_traits.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+namespace sdbusplus
+{
+namespace utility
+{
+
+/** has_const_iterator - Determine if type has const iterator
+ *
+ *  @tparam T - Type to be tested.
+ *
+ *  @value A value as to whether or not the type supports iteration
+ */
+template <typename T> struct has_const_iterator
+{
+  private:
+    typedef char yes;
+    typedef struct
+    {
+        char array[2];
+    } no;
+
+    template <typename C>
+    static constexpr yes test(typename C::const_iterator*);
+    template <typename C> static constexpr no test(...);
+
+  public:
+    static constexpr bool value = sizeof(test<T>(0)) == sizeof(yes);
+};
+
+/** has_emplace_method - Determine if type has a method template named emplace
+ *
+ *  @tparam T - Type to be tested.
+ *
+ *  @value A value as to whether or not the type has an emplace method
+ */
+template <typename T> struct has_emplace_method
+{
+  private:
+    struct dummy
+    {
+    };
+
+    template <typename C, typename P>
+    static constexpr auto test(P* p)
+        -> decltype(std::declval<C>().emplace(*p), std::true_type());
+
+    template <typename, typename> static std::false_type test(...);
+
+  public:
+    static constexpr bool value =
+        std::is_same<std::true_type, decltype(test<T, dummy>(nullptr))>::value;
+};
+
+/** has_emplace_method - Determine if type has a method template named
+ * emplace_back
+ *
+ *  @tparam T - Type to be tested.
+ *
+ *  @value A value as to whether or not the type has an emplace_back method
+ */
+template <typename T> struct has_emplace_back_method
+{
+  private:
+    struct dummy
+    {
+    };
+
+    template <typename C, typename P>
+    static constexpr auto test(P* p)
+        -> decltype(std::declval<C>().emplace_back(*p), std::true_type());
+
+    template <typename, typename> static std::false_type test(...);
+
+  public:
+    static constexpr bool value =
+        std::is_same<std::true_type, decltype(test<T, dummy>(nullptr))>::value;
+};
+
+} // namespace utility
+} // namespace sdbusplus
diff --git a/test/message/append.cpp b/test/message/append.cpp
index 7782f02..c56402a 100644
--- a/test/message/append.cpp
+++ b/test/message/append.cpp
@@ -2,6 +2,8 @@
 #include <cassert>
 #include <sdbusplus/message.hpp>
 #include <sdbusplus/bus.hpp>
+#include <unordered_map>
+#include <set>
 
 // Global to share the dbus type string between client and server.
 static std::string verifyTypeString;
@@ -384,6 +386,139 @@
         b.call_noreply(m);
     }
 
+    // Test unordered_map.
+    {
+        auto m = newMethodCall__test(b);
+        std::unordered_map<std::string, int> s = {{"asdf", 3}, {"jkl;", 4}};
+        m.append(1, s, 2);
+        verifyTypeString = "ia{si}i";
+
+        struct verify
+        {
+            static void op(sd_bus_message* m)
+            {
+                int32_t a = 0;
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 1);
+
+                auto rc = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+                                                         "{si}");
+                assert(0 <= rc);
+
+                rc = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY,
+                                                    "si");
+                assert(0 <= rc);
+
+                const char* s = nullptr;
+                sd_bus_message_read_basic(m, 's', &s);
+                sd_bus_message_read_basic(m, 'i', &a);
+                assert((0 == strcmp("asdf", s) && a == 3) ||
+                       (a = 4 && 0 == strcmp("jkl;", s)));
+
+                assert(1 == sd_bus_message_at_end(m, false));
+                sd_bus_message_exit_container(m);
+
+                rc = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY,
+                                                    "si");
+                assert(0 <= rc);
+
+                sd_bus_message_read_basic(m, 's', &s);
+                sd_bus_message_read_basic(m, 'i', &a);
+                assert((0 == strcmp("asdf", s) && a == 3) ||
+                       (a = 4 && 0 == strcmp("jkl;", s)));
+
+                assert(1 == sd_bus_message_at_end(m, false));
+                sd_bus_message_exit_container(m);
+
+                assert(1 == sd_bus_message_at_end(m, false));
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+    // Test set.
+    {
+        auto m = newMethodCall__test(b);
+        std::set<std::string> s = {{"asdf"}, {"jkl;"}};
+        m.append(1, s, 2);
+        verifyTypeString = "iasi";
+
+        struct verify
+        {
+            static void op(sd_bus_message* m)
+            {
+                int32_t a = 0;
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 1);
+
+                auto rc =
+                    sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s");
+                assert(0 <= rc);
+
+                const char* s = nullptr;
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("asdf", s));
+
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("jkl;", s));
+
+                assert(1 == sd_bus_message_at_end(m, false));
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+    // Test array.
+    {
+        auto m = newMethodCall__test(b);
+        std::array<std::string, 3> s{"1", "2", "3"};
+        m.append(1, s, 2);
+        verifyTypeString = "iasi";
+
+        struct verify
+        {
+            static void op(sd_bus_message* m)
+            {
+                int32_t a = 0;
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 1);
+
+                auto rc =
+                    sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s");
+                assert(0 <= rc);
+
+                const char* s = nullptr;
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("1", s));
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("2", s));
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("3", s));
+                assert(1 == sd_bus_message_at_end(m, false));
+
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
     // Test tuple.
     {
         auto m = newMethodCall__test(b);
diff --git a/test/message/read.cpp b/test/message/read.cpp
index 8c2c4ea..cd31b61 100644
--- a/test/message/read.cpp
+++ b/test/message/read.cpp
@@ -2,6 +2,8 @@
 #include <cassert>
 #include <sdbusplus/message.hpp>
 #include <sdbusplus/bus.hpp>
+#include <unordered_map>
+#include <set>
 
 // Global to share the dbus type string between client and server.
 static std::string verifyTypeString;
@@ -304,6 +306,60 @@
         b.call_noreply(m);
     }
 
+    // Test unordered_map.
+    {
+        auto m = newMethodCall__test(b);
+        std::unordered_map<std::string, int> s = {{"asdf", 3}, {"jkl;", 4}};
+        m.append(1, s, 2);
+        verifyTypeString = "ia{si}i";
+
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a = 0, b = 0;
+                std::unordered_map<std::string, int> s{};
+
+                m.read(a, s, b);
+                assert(a == 1);
+                assert(s.size() == 2);
+                assert(s["asdf"] == 3);
+                assert(s["jkl;"] == 4);
+                assert(b == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+    // Test set.
+    {
+        auto m = newMethodCall__test(b);
+        std::set<std::string> s = {{"asdf"}, {"jkl;"}};
+        m.append(1, s, 2);
+        verifyTypeString = "iasi";
+
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a = 0, b = 0;
+                std::set<std::string> s{};
+
+                m.read(a, s, b);
+                assert(a == 1);
+                assert(s.size() == 2);
+                assert(s.find("asdf") != s.end());
+                assert(s.find("jkl;") != s.end());
+                assert(b == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
     // Test tuple.
     {
         auto m = newMethodCall__test(b);