Loading media/utils/include/mediautils/StaticStringView.h 0 → 100644 +207 −0 Original line number Diff line number Diff line /* * 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") media/utils/tests/Android.bp +10 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,16 @@ cc_test { ], } cc_test { name: "static_string_tests", defaults: ["libmediautils_tests_defaults"], srcs: [ "static_string_view_tests.cpp", ], } cc_test { name: "timecheck_tests", Loading media/utils/tests/static_string_view_tests.cpp 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * 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. */ #define LOG_TAG "StaticStringViewTests" #include <mediautils/StaticStringView.h> #include <gtest/gtest.h> #include <log/log.h> using namespace android::mediautils; template <auto& T, class = void> struct CanCreate : std::false_type {}; template <auto& T> struct CanCreate<T, typename std::void_t<decltype(StaticStringView::create<T>)>> : std::true_type { }; static constexpr std::array<char, 2> global = {'a', 'b'}; TEST(StaticStringViewTests, CreateTicket) { // This will always fail due to template param binding rules // const std::array<char,2> nonstatic = {'a', 'b'}; // static_assert(can_assign<nonstatic>::value == false); static std::array<char, 2> nonconst = {'a', 'b'}; static const std::array<char, 2> nonconstexpr = {'a', 'b'}; static constexpr std::array<int, 2> nonchar = {1, 2}; static constexpr size_t nonarray = 2; static_assert(CanCreate<nonconst>::value == false); static_assert(CanCreate<nonarray>::value == false); static_assert(CanCreate<nonchar>::value == false); static_assert(CanCreate<nonconstexpr>::value == false); static constexpr std::array<char, 2> scoped = {'a', 'b'}; constexpr StaticStringView Ticket1 = StaticStringView::create<global>(); constexpr StaticStringView Ticket2 = StaticStringView::create<scoped>(); const StaticStringView Ticket3 = StaticStringView::create<scoped>(); EXPECT_EQ(Ticket3, Ticket2); EXPECT_EQ(Ticket1.getStringView(), Ticket2.getStringView()); EXPECT_EQ(std::string_view{"ab"}, Ticket1.getStringView()); } TEST(StaticStringViewTests, CompileTimeConvert) { static constexpr std::array<char, 4> converted = StaticStringView::toStdArray("test"); constexpr StaticStringView ticket = StaticStringView::create<converted>(); EXPECT_EQ(ticket, std::string_view{"test"}); // Unchecked constexpr construction static const std::array<char, 5> converted2 = StaticStringView::toStdArray("test2"); constexpr auto ticket2 = StaticStringView::create<converted2, false>(); EXPECT_EQ(ticket2, std::string_view{"test2"}); constexpr char stack_array[4] = {'a', 'b', 'c', '\0'}; static constexpr auto converted3 = StaticStringView::toStdArray(stack_array); constexpr auto ticket3 = StaticStringView::create<converted3>(); EXPECT_EQ(ticket3, std::string_view{"abc"}); } TEST(StaticStringViewTests, CompileTimeConcat) { // temporaries should not be static to prevent odr use constexpr std::array<char, 3> arr1 = {'a', 'b', 'c'}; constexpr std::array<char, 4> arr2 = {'d', 'e', 'f', 'g'}; static constexpr std::array<char, 7> res = StaticStringView::concatArray(arr1, arr2); static constexpr std::array<char, 7> expected = {'a', 'b', 'c', 'd', 'e', 'f', 'g'}; EXPECT_EQ(res, expected); } TEST(StaticStringViewTests, StringViewForwarding) { static constexpr auto converted = StaticStringView::toStdArray("test"); constexpr auto ticket = StaticStringView::create<converted>(); EXPECT_EQ(ticket.length(), ticket.getStringView().length()); EXPECT_TRUE(ticket == ticket.getStringView()); EXPECT_TRUE(ticket == ticket); EXPECT_TRUE(ticket.getStringView() == ticket); EXPECT_TRUE(ticket > "abc"); EXPECT_TRUE("abc" < ticket); } Loading
media/utils/include/mediautils/StaticStringView.h 0 → 100644 +207 −0 Original line number Diff line number Diff line /* * 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")
media/utils/tests/Android.bp +10 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,16 @@ cc_test { ], } cc_test { name: "static_string_tests", defaults: ["libmediautils_tests_defaults"], srcs: [ "static_string_view_tests.cpp", ], } cc_test { name: "timecheck_tests", Loading
media/utils/tests/static_string_view_tests.cpp 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * 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. */ #define LOG_TAG "StaticStringViewTests" #include <mediautils/StaticStringView.h> #include <gtest/gtest.h> #include <log/log.h> using namespace android::mediautils; template <auto& T, class = void> struct CanCreate : std::false_type {}; template <auto& T> struct CanCreate<T, typename std::void_t<decltype(StaticStringView::create<T>)>> : std::true_type { }; static constexpr std::array<char, 2> global = {'a', 'b'}; TEST(StaticStringViewTests, CreateTicket) { // This will always fail due to template param binding rules // const std::array<char,2> nonstatic = {'a', 'b'}; // static_assert(can_assign<nonstatic>::value == false); static std::array<char, 2> nonconst = {'a', 'b'}; static const std::array<char, 2> nonconstexpr = {'a', 'b'}; static constexpr std::array<int, 2> nonchar = {1, 2}; static constexpr size_t nonarray = 2; static_assert(CanCreate<nonconst>::value == false); static_assert(CanCreate<nonarray>::value == false); static_assert(CanCreate<nonchar>::value == false); static_assert(CanCreate<nonconstexpr>::value == false); static constexpr std::array<char, 2> scoped = {'a', 'b'}; constexpr StaticStringView Ticket1 = StaticStringView::create<global>(); constexpr StaticStringView Ticket2 = StaticStringView::create<scoped>(); const StaticStringView Ticket3 = StaticStringView::create<scoped>(); EXPECT_EQ(Ticket3, Ticket2); EXPECT_EQ(Ticket1.getStringView(), Ticket2.getStringView()); EXPECT_EQ(std::string_view{"ab"}, Ticket1.getStringView()); } TEST(StaticStringViewTests, CompileTimeConvert) { static constexpr std::array<char, 4> converted = StaticStringView::toStdArray("test"); constexpr StaticStringView ticket = StaticStringView::create<converted>(); EXPECT_EQ(ticket, std::string_view{"test"}); // Unchecked constexpr construction static const std::array<char, 5> converted2 = StaticStringView::toStdArray("test2"); constexpr auto ticket2 = StaticStringView::create<converted2, false>(); EXPECT_EQ(ticket2, std::string_view{"test2"}); constexpr char stack_array[4] = {'a', 'b', 'c', '\0'}; static constexpr auto converted3 = StaticStringView::toStdArray(stack_array); constexpr auto ticket3 = StaticStringView::create<converted3>(); EXPECT_EQ(ticket3, std::string_view{"abc"}); } TEST(StaticStringViewTests, CompileTimeConcat) { // temporaries should not be static to prevent odr use constexpr std::array<char, 3> arr1 = {'a', 'b', 'c'}; constexpr std::array<char, 4> arr2 = {'d', 'e', 'f', 'g'}; static constexpr std::array<char, 7> res = StaticStringView::concatArray(arr1, arr2); static constexpr std::array<char, 7> expected = {'a', 'b', 'c', 'd', 'e', 'f', 'g'}; EXPECT_EQ(res, expected); } TEST(StaticStringViewTests, StringViewForwarding) { static constexpr auto converted = StaticStringView::toStdArray("test"); constexpr auto ticket = StaticStringView::create<converted>(); EXPECT_EQ(ticket.length(), ticket.getStringView().length()); EXPECT_TRUE(ticket == ticket.getStringView()); EXPECT_TRUE(ticket == ticket); EXPECT_TRUE(ticket.getStringView() == ticket); EXPECT_TRUE(ticket > "abc"); EXPECT_TRUE("abc" < ticket); }