util/str: Add string concatentation methods

Change-Id: I5caf8e0731eb9ac0f18b84d25256ea0068fab03c
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/README.md b/README.md
index bc9991b..496b6b5 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
 * A [movable](src/stdplus/handle/managed.hpp) and [copyable](src/stdplus/handle/copyable.hpp) RAII helper wrapper which is used for wrapping c-native types that have custom destruction or copy reference logic.
 * [Functions](src/stdplus/signal.hpp) for trivially configuring signals without having to do the normal signal set operations from libc
 * [C-Style Error Handler](src/stdplus/util/cexec.hpp) that wrap c-style functions which return errnos and negative error values into functions that throw c++ exceptions.
+* [String Utilities](src/stdplus/util/string.hpp) that focus on providing helpful wrappers like efficient string append and concatenation.
 
 ## Dependencies
 
diff --git a/src/meson.build b/src/meson.build
index a6e82b9..aadb68d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -31,4 +31,5 @@
 
 install_headers(
   'stdplus/util/cexec.hpp',
+  'stdplus/util/string.hpp',
   subdir: 'stdplus/util')
diff --git a/src/stdplus/util/string.hpp b/src/stdplus/util/string.hpp
new file mode 100644
index 0000000..4e37e3b
--- /dev/null
+++ b/src/stdplus/util/string.hpp
@@ -0,0 +1,57 @@
+#pragma once
+#include <cstring>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace stdplus
+{
+namespace util
+{
+namespace detail
+{
+
+template <typename... Views>
+void strAppendViews(std::string& dst, Views... views)
+{
+    dst.reserve((dst.size() + ... + views.size()));
+    (dst.append(views), ...);
+}
+
+} // namespace detail
+
+/** @brief Appends multiple strings to the end of the destination string
+ *         in the most optimal way for the given inputs.
+ *
+ *  @param[in, out] dst - The string being appended to
+ *  @param[in] ...strs  - An arbitrary number of strings to concatenate
+ */
+template <typename... Strs>
+void strAppend(std::string& dst, const Strs&... strs)
+{
+    detail::strAppendViews(dst, std::string_view(strs)...);
+}
+
+/** @brief Concatenates multiple strings together in the most optimal
+ *         way for the given inputs.
+ *
+ *  @param[in] ...strs - An arbitrary number of strings to concatenate
+ *  @return The concatenated result string
+ */
+template <typename... Strs>
+std::string strCat(const Strs&... strs)
+{
+    std::string ret;
+    strAppend(ret, strs...);
+    return ret;
+}
+template <typename... Strs>
+std::string strCat(std::string&& in, const Strs&... strs)
+{
+    std::string ret = std::move(in);
+    strAppend(ret, strs...);
+    return ret;
+}
+
+} // namespace util
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index bcd016e..c8fddf0 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -6,6 +6,7 @@
   'handle/copyable',
   'handle/managed',
   'util/cexec',
+  'util/string',
 ]
 
 foreach t : tests
diff --git a/test/util/string.cpp b/test/util/string.cpp
new file mode 100644
index 0000000..8ab4e81
--- /dev/null
+++ b/test/util/string.cpp
@@ -0,0 +1,39 @@
+#include <gtest/gtest.h>
+#include <stdplus/util/string.hpp>
+#include <string>
+#include <string_view>
+
+namespace stdplus
+{
+namespace util
+{
+namespace
+{
+
+using namespace std::string_literals;
+using namespace std::string_view_literals;
+
+TEST(StrCat, NoStr)
+{
+    EXPECT_EQ("", strCat());
+}
+
+TEST(StrCat, SingleStr)
+{
+    EXPECT_EQ("func", strCat("func"));
+}
+
+TEST(StrCat, Multi)
+{
+    EXPECT_EQ("func world test", strCat("func", " world"sv, " test"s));
+}
+
+TEST(StrCat, MoveStr)
+{
+    EXPECT_EQ("func", strCat("func"s));
+    EXPECT_EQ("func world", strCat("func"s, " world"));
+}
+
+} // namespace
+} // namespace util
+} // namespace stdplus