message: add unpack method

Add an unpack method that allows reading from the message as
r-values.  This simplifies the pattern:

```
    foo f{};
    bar b{};
    msg.read(f,b);

    // Can now be written as...

    auto [f, b] = msg.unpack<foo, bar>();
```

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: Ic2ae7f2c52d41702b8c7c3af6a2efb21558a7579
diff --git a/README.md b/README.md
index 64e5984..3bf3704 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,9 @@
 
 std::vector<std::tuple<uint32_t, std::string, message::object_path>> users;
 reply.read(users);
+    // or
+auto users = reply.unpack<
+    std::vector<std::tuple<uint32_t, std::string, message::object_path>>>();
 ```
 
 In a few, relatively succinct, C++ lines this snippet will create a D-Bus
diff --git a/example/list-users.cpp b/example/list-users.cpp
index 23913b0..f7ab475 100644
--- a/example/list-users.cpp
+++ b/example/list-users.cpp
@@ -18,8 +18,9 @@
                           "org.freedesktop.login1.Manager", "ListUsers");
     auto reply = b.call(m);
 
-    std::vector<std::tuple<uint32_t, std::string, message::object_path>> users;
-    reply.read(users);
+    using return_type =
+        std::vector<std::tuple<uint32_t, std::string, message::object_path>>;
+    auto users = reply.unpack<return_type>();
 
     for (auto& user : users)
     {
diff --git a/include/sdbusplus/message.hpp b/include/sdbusplus/message.hpp
index 749522e..4824997 100644
--- a/include/sdbusplus/message.hpp
+++ b/include/sdbusplus/message.hpp
@@ -12,6 +12,7 @@
 #include <exception>
 #include <memory>
 #include <optional>
+#include <tuple>
 #include <type_traits>
 #include <utility>
 
@@ -156,6 +157,32 @@
                                  std::forward<Args>(args)...);
     }
 
+    /** @brief Perform sd_bus_message_read with results returned.
+     *
+     *  @tparam ...Args - Type of items to read from the message.
+     *  @return One of { void, Args, std::tuple<Args...> }.
+     */
+    template <typename... Args>
+    auto unpack()
+    {
+        if constexpr (sizeof...(Args) == 0)
+        {
+            return;
+        }
+        else if constexpr (sizeof...(Args) == 1)
+        {
+            std::tuple_element_t<0, std::tuple<Args...>> r{};
+            read(r);
+            return r;
+        }
+        else
+        {
+            std::tuple<Args...> r{};
+            std::apply([this](auto&&... v) { this->read(v...); }, r);
+            return r;
+        }
+    }
+
     /** @brief Get the dbus bus from the message. */
     // Forward declare.
     auto get_bus() const;
diff --git a/test/message/read.cpp b/test/message/read.cpp
index 6131c24..44253c6 100644
--- a/test/message/read.cpp
+++ b/test/message/read.cpp
@@ -649,4 +649,44 @@
     EXPECT_EQ(msv, ret_msv);
 }
 
+// Unpack tests.
+// Since unpack uses read, we're mostly just testing the compilation.
+// Duplicate a few tests from Read using 'unpack'.
+
+TEST_F(ReadTest, UnpackSingleVector)
+{
+    const std::vector<int> vi{1, 2, 3, 4};
+
+    {
+        testing::InSequence seq;
+        expect_enter_container(SD_BUS_TYPE_ARRAY, "i");
+        for (const auto& i : vi)
+        {
+            expect_at_end(false, 0);
+            expect_basic<int>(SD_BUS_TYPE_INT32, i);
+        }
+        expect_at_end(false, 1);
+        expect_exit_container();
+    }
+
+    auto ret_vi = new_message().unpack<std::vector<int>>();
+    EXPECT_EQ(vi, ret_vi);
+}
+
+TEST_F(ReadTest, UnpackMultiple)
+{
+    const std::tuple<int, std::string, bool> tisb{3, "hi", false};
+
+    {
+        testing::InSequence seq;
+        expect_basic<int>(SD_BUS_TYPE_INT32, std::get<0>(tisb));
+        expect_basic<const char*>(SD_BUS_TYPE_STRING,
+                                  std::get<1>(tisb).c_str());
+        expect_basic<int>(SD_BUS_TYPE_BOOLEAN, std::get<2>(tisb));
+    }
+
+    auto ret_tisb = new_message().unpack<int, std::string, bool>();
+    EXPECT_EQ(tisb, ret_tisb);
+}
+
 } // namespace