str/cexpr: Add a function to make a constexpr generated string

This allows the user to generate an arbitrary length string at compile
time and convert it automatically to an std::array that is embedded in
the program for zero overhead runtime strings.

Change-Id: Ib6c2dd20cac53bb55e7a32e2fca194bdf0e06211
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index dc4de82..c3bfdfe 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -10,6 +10,7 @@
   'stdplus/raw.hpp',
   'stdplus/signal.hpp',
   'stdplus/str/cat.hpp',
+  'stdplus/str/cexpr.hpp',
   'stdplus/util/cexec.hpp',
   'stdplus/util/string.hpp',
   'stdplus/zstring.hpp',
diff --git a/include/stdplus/str/cexpr.hpp b/include/stdplus/str/cexpr.hpp
new file mode 100644
index 0000000..a3d5f1c
--- /dev/null
+++ b/include/stdplus/str/cexpr.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include <algorithm>
+#include <array>
+#include <string_view>
+
+namespace stdplus
+{
+
+template <auto f, bool nul>
+consteval auto cexprToStrArr()
+{
+    std::array<typename decltype(f())::value_type, f().size() + (nul ? 1 : 0)>
+        ret;
+    {
+        auto res = f();
+        std::copy(res.begin(), res.end(), ret.begin());
+        if constexpr (nul)
+        {
+            ret[ret.size() - 1] = '\0';
+        }
+    }
+    return ret;
+}
+
+template <auto f, bool nul>
+inline constexpr auto cexprStrArr = cexprToStrArr<f, nul>();
+
+template <auto f>
+consteval auto cexprToSv()
+{
+    constexpr auto& d = cexprStrArr<f, /*nul=*/false>;
+    return std::basic_string_view(d.begin(), d.end());
+}
+
+template <auto f>
+inline constexpr auto cexprSv = cexprToSv<f>();
+
+} // namespace stdplus
diff --git a/include/stdplus/zstring_view.hpp b/include/stdplus/zstring_view.hpp
index 79003bd..0c18bfb 100644
--- a/include/stdplus/zstring_view.hpp
+++ b/include/stdplus/zstring_view.hpp
@@ -1,6 +1,7 @@
 #pragma once
 #include <fmt/core.h>
 
+#include <stdplus/str/cexpr.hpp>
 #include <stdplus/zstring.hpp>
 
 #include <stdexcept>
@@ -318,6 +319,19 @@
 }
 } // namespace zstring_view_literals
 
+template <auto f>
+consteval auto cexprToZsv()
+{
+    constexpr auto& d = cexprStrArr<f, /*nul=*/true>;
+    static_assert(detail::zstring_find_term(d.data(), d.size() - 1, d.size()) >=
+                  0);
+    return detail::unsafe_zstring_view(
+        std::basic_string_view(d.begin(), d.end() - 1));
+}
+
+template <auto f>
+inline constexpr auto cexprZsv = cexprToZsv<f>();
+
 } // namespace stdplus
 
 #define zstring_view_all(char_t, pfx)                                          \
diff --git a/src/meson.build b/src/meson.build
index 2a08ac5..7c4dabf 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -51,6 +51,7 @@
   'raw.cpp',
   'signal.cpp',
   'str/cat.cpp',
+  'str/cexpr.cpp',
   'util/cexec.cpp',
   'zstring.cpp',
   'zstring_view.cpp',
diff --git a/src/str/cexpr.cpp b/src/str/cexpr.cpp
new file mode 100644
index 0000000..b672808
--- /dev/null
+++ b/src/str/cexpr.cpp
@@ -0,0 +1 @@
+#include <stdplus/str/cexpr.hpp>
diff --git a/test/meson.build b/test/meson.build
index 2f1d7f9..901527d 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,6 +7,7 @@
   'raw': [stdplus_dep, gmock_dep, gtest_main_dep],
   'signal': [stdplus_dep, gtest_main_dep],
   'str/cat': [stdplus_dep, gtest_main_dep],
+  'str/cexpr': [stdplus_dep, gtest_main_dep],
   'util/cexec': [stdplus_dep, gtest_main_dep],
   'zstring': [stdplus_dep, gtest_main_dep],
   'zstring_view': [stdplus_dep, gtest_main_dep],
diff --git a/test/str/cexpr.cpp b/test/str/cexpr.cpp
new file mode 100644
index 0000000..557591c
--- /dev/null
+++ b/test/str/cexpr.cpp
@@ -0,0 +1,44 @@
+#include <stdplus/str/cexpr.hpp>
+#include <stdplus/zstring_view.hpp>
+
+#include <string>
+#include <string_view>
+
+#include <gtest/gtest.h>
+
+using namespace std::string_view_literals;
+using namespace std::string_literals;
+
+namespace stdplus
+{
+
+TEST(Constexpr, SvFromSv)
+{
+    EXPECT_EQ((std::array{'a', 'b', 'c', '\0'}),
+              (cexprStrArr<[]() { return "abc"sv; }, true>));
+    EXPECT_EQ((std::array{'a', 'b', 'c'}),
+              (cexprStrArr<[]() { return "abc"sv; }, false>));
+    EXPECT_EQ("abc"sv, cexprZsv<[]() { return "abc"sv; }>);
+    EXPECT_EQ("abc"sv, cexprSv<[]() { return "abc"sv; }>);
+}
+
+TEST(Constexpr, SvFromSmallStr)
+{
+    EXPECT_EQ((std::array{'a', 'b', 'c', '\0'}),
+              (cexprStrArr<[]() { return "abc"s; }, true>));
+    EXPECT_EQ((std::array{'a', 'b', 'c'}),
+              (cexprStrArr<[]() { return "abc"s; }, false>));
+    EXPECT_EQ("abc"sv, cexprSv<[]() { return "abc"s; }>);
+    EXPECT_EQ("abc"sv, cexprZsv<[]() { return "abc"s; }>);
+}
+
+TEST(Constexpr, SvFromAllocStr)
+{
+    constexpr auto cb = []() {
+        return "abcdefg"s.append("hijklmnopqrstuvwxyz"sv);
+    };
+    EXPECT_EQ("abcdefghijklmnopqrstuvwxyz"sv, cexprSv<cb>);
+    EXPECT_EQ("abcdefghijklmnopqrstuvwxyz"sv, cexprZsv<cb>);
+}
+
+} // namespace stdplus