Add StaticStringView Utility
We frequently pass and store strings that are generated/known at
compile-time. Since these strings are constant, we can place them in
static storage, and be guaranteed that their lifetime is valid
throughout the duration of the program. This allows us to pass
non-owning references (such as string_view) without any concern for
lifetime validity.
The StaticStringView is a string_view whose referant is a static,
constant string. Additionally, we can concatenate and
convert from string literals at compile time for typical log-string
generation.
Test: atest StaticStringTests
Bug: 238654698
Change-Id: I067bd4330dcc39dc0bde269fb67306484c275faa
diff --git a/media/utils/include/mediautils/StaticStringView.h b/media/utils/include/mediautils/StaticStringView.h
new file mode 100644
index 0000000..14be240
--- /dev/null
+++ b/media/utils/include/mediautils/StaticStringView.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string_view>
+#include <type_traits>
+
+#pragma push_macro("EXPLICIT_CONVERSION_GENERATE_OPERATOR")
+#undef EXPLICIT_CONVERSION_GENERATE_OPERATOR
+#define EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, op) \
+ friend constexpr bool operator op(T lhs, T rhs) { \
+ return operator op(static_cast<U>(lhs), static_cast<U>(rhs)); \
+ } \
+ friend constexpr bool operator op(T lhs, U rhs) { \
+ return operator op(static_cast<U>(lhs), rhs); \
+ } \
+ friend constexpr bool operator op(U lhs, T rhs) { \
+ return operator op(lhs, static_cast<U>(rhs)); \
+ }
+
+#pragma push_macro("EXPLICIT_CONVERSION_GENERATE_COMPARISON_OPERATORS")
+#undef EXPLICIT_CONVERSION_GENERATE_COMPARISON_OPERATORS
+// Generate comparison operator friend functions for types (appropriately
+// const/ref qualified) where T is **explicitly** convertible to U.
+#define EXPLICIT_CONVERSION_GENERATE_COMPARISON_OPERATORS(T, U) \
+ EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, ==) \
+ EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, !=) \
+ EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, <) \
+ EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, <=) \
+ EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, >) \
+ EXPLICIT_CONVERSION_GENERATE_OPERATOR(T, U, >=)
+
+namespace android::mediautils {
+
+// This class a reference to a string with static storage duration
+// which is const (i.e. a string view). We expose an identical API to
+// string_view, however we do not publicly inherit to avoid potential mis-use of
+// non-virtual dtors/methods.
+//
+// We can create APIs which consume only static strings, which
+// avoids allocation/deallocation of the string locally, as well as potential
+// lifetime issues caused by consuming raw pointers (or string_views).
+// Equivalently, a string_view which is always valid, and whose underlying data
+// can never change.
+//
+// In most cases, the string_view should be initialized at compile time (and there are
+// helpers to do so below). In order to initialize a non-constexpr array,
+// the second template param must be false (i.e. opt-in).
+// Construction/usage as follows (constexpr required unless second template param is false):
+//
+// constexpr static std::array<char, 12> debugString = toStdArray("MyMethodName");
+// constexpr auto myStaticStringView = StaticStringView::create<debugString>();
+// const auto size_t length = myStaticStringView.length() // can call any string_view methods
+// globalLog(myStaticStringView, ...); // Pass to APIs consuming StaticStringViews
+//
+struct StaticStringView final : private std::string_view {
+ template <typename T>
+ struct is_const_char_array : std::false_type {};
+
+ // Use templated value helper
+ template <size_t N>
+ struct is_const_char_array<const std::array<char, N>> : std::true_type {};
+
+ template <typename T>
+ static constexpr bool is_const_char_array_v =
+ is_const_char_array<std::remove_reference_t<T>>::value;
+
+ template <auto& val, std::enable_if_t<is_const_char_array_v<decltype(val)>, bool> Check = true>
+ static constexpr StaticStringView create() {
+ if constexpr (Check) {
+ // If this static_assert fails to compile, this method was called
+ // with a non-constexpr
+ static_assert(val[0]);
+ }
+ return StaticStringView{val.data(), val.size()};
+ }
+
+ // We can copy/move assign/construct from other StaticStringViews as their validity is already
+ // ensured
+ constexpr StaticStringView(const StaticStringView& other) = default;
+ constexpr StaticStringView& operator=(const StaticStringView& other) = default;
+ constexpr StaticStringView(StaticStringView&& other) = default;
+ constexpr StaticStringView& operator=(StaticStringView&& other) = default;
+
+ // Explicitly convert to a std::string_view (this is a strict loss of
+ // information so should only be used across APIs which intend to consume
+ // any std::string_view).
+ constexpr std::string_view getStringView() const { return *this; }
+
+ // The following methods expose an identical API to std::string_view
+ using std::string_view::begin;
+ using std::string_view::cbegin;
+ using std::string_view::cend;
+ using std::string_view::crbegin;
+ using std::string_view::crend;
+ using std::string_view::end;
+ using std::string_view::rbegin;
+ using std::string_view::rend;
+ using std::string_view::operator[];
+ using std::string_view::at;
+ using std::string_view::back;
+ using std::string_view::data;
+ using std::string_view::empty;
+ using std::string_view::front;
+ using std::string_view::length;
+ using std::string_view::max_size;
+ using std::string_view::size;
+ // These modifiers are valid because the resulting view is a
+ // substring of the original static string
+ using std::string_view::remove_prefix;
+ using std::string_view::remove_suffix;
+ // Skip swap
+ using std::string_view::compare;
+ using std::string_view::copy;
+ using std::string_view::find;
+ using std::string_view::find_first_not_of;
+ using std::string_view::find_first_of;
+ using std::string_view::find_last_not_of;
+ using std::string_view::find_last_of;
+ using std::string_view::rfind;
+ using std::string_view::substr;
+#if __cplusplus >= 202202L
+ using std::string_view::ends_with;
+ using std::string_view::starts_with;
+#endif
+ using std::string_view::npos;
+
+ // Non-member friend functions to follow. Identical API to std::string_view
+ template <class CharT, class Traits>
+ friend std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+ StaticStringView v) {
+ return os << static_cast<std::string_view&>(v);
+ }
+
+ EXPLICIT_CONVERSION_GENERATE_COMPARISON_OPERATORS(const StaticStringView&,
+ const std::string_view&)
+
+ private:
+ constexpr StaticStringView(const char* ptr, size_t sz) : std::string_view(ptr, sz){};
+
+ public:
+ // The next two functions are logically consteval (only avail in c++20).
+ // We can't use templates as params, as they would require references to
+ // static which would unnecessarily bloat executable size.
+ template <typename T, size_t N, size_t M>
+ static constexpr std::array<T, N + M> concatArray(const std::array<T, N>& a,
+ const std::array<T, M>& b) {
+ std::array<T, N + M> res{};
+ for (size_t i = 0; i < N; i++) {
+ res[i] = a[i];
+ }
+ for (size_t i = 0; i < M; i++) {
+ res[N + i] = b[i];
+ }
+ return res;
+ }
+
+ static void arrayIsNotNullTerminated();
+
+ // This method should only be called on C-style char arrays which are
+ // null-terminated. Calling this method on a char array with intermediate null
+ // characters (i.e. "hello\0" or "hel\0lo" will result in a std::array with null
+ // characters, which is most likely not intended.
+ // We attempt to detect a non-null terminated char array at link-time, but
+ // this is best effort. A consequence of this approach is that this method
+ // will fail to link for extern args, or when not inlined. Since this method
+ // is intended to be used constexpr, this is not an issue.
+ template <size_t N>
+ static constexpr std::array<char, N - 1> toStdArray(const char (&input)[N]) {
+ std::array<char, N - 1> res{};
+ for (size_t i = 0; i < N - 1; i++) {
+ res[i] = input[i];
+ }
+ // A workaround to generate a link-time error if toStdArray is not called on
+ // a null-terminated char array.
+ if (input[N - 1] != 0) arrayIsNotNullTerminated();
+ return res;
+ }
+};
+} // namespace android::mediautils
+
+// Specialization of std::hash for use with std::unordered_map
+namespace std {
+template <>
+struct hash<android::mediautils::StaticStringView> {
+ constexpr size_t operator()(const android::mediautils::StaticStringView& val) {
+ return std::hash<std::string_view>{}(val.getStringView());
+ }
+};
+} // namespace std
+
+#pragma pop_macro("EXPLICIT_CONVERSION_GENERATE_OPERATOR")
+#pragma pop_macro("EXPLICIT_CONVERSION_GENERATE_COMPARISON_OPERATORS")