From 4cccd423199bf179039c0adf68425af6ad6e6241 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Fri, 26 Jun 2026 19:57:51 -0700 Subject: [PATCH 1/3] Add `_bst` user-defined literal for BSTR-shaped wide strings C++20 only. Captures the literal as a class-type NTTP so storage is sized exactly to the literal length; constexpr-constructible, no heap, no lifetime hazards. void Use(BSTR); Use(L\"foo\"_bst); Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/wil/stl.h | 58 +++++++++++++++++++++++++++++++++++++++++++++- tests/StlTests.cpp | 19 +++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/include/wil/stl.h b/include/wil/stl.h index 5abd78d2..4d085da5 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -252,11 +252,67 @@ inline auto str_raw_ptr(basic_zstring_view str) inline namespace literals { +#if __WI_LIBCPP_STD_VER >= 20 + /** + A statically-allocated, BSTR-shaped literal: a length-prefixed wide string whose data pointer is a valid + BSTR (usable with SysStringLen, SysStringByteLen, wcslen). No heap allocation; size is the literal's exact + length. Lifetime is tied to the literal object itself. + + Example: + void Use(BSTR); + Use(L"foo"_bst); + */ + template + struct wchar_literal_storage + { + wchar_t value[N]; + constexpr wchar_literal_storage(const wchar_t (&str)[N]) WI_NOEXCEPT + { + for (std::size_t i = 0; i < N; ++i) + { + value[i] = str[i]; + } + } + }; + + template + wchar_literal_storage(const wchar_t (&)[N]) -> wchar_literal_storage; + + // N includes the trailing L'\0'; the BSTR byte-length prefix excludes it. + template + struct bstr_literal_t + { + unsigned long m_byte_length; + wchar_t m_data[N]; + + constexpr bstr_literal_t(const wchar_t (&text)[N]) WI_NOEXCEPT : + m_byte_length(static_cast((N - 1) * sizeof(wchar_t))), m_data{} + { + for (std::size_t i = 0; i < N; ++i) + { + m_data[i] = text[i]; + } + } + + WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT + { + return const_cast(&m_data[0]); + } + }; + + template + WI_NODISCARD constexpr auto operator""_bst() WI_NOEXCEPT + { + return bstr_literal_t{Lit.value}; + } + +#endif // __WI_LIBCPP_STD_VER >= 20 + constexpr zstring_view operator""_zv(const char* str, std::size_t len) noexcept { return {str, len}; } - + constexpr zwstring_view operator""_zv(const wchar_t* str, std::size_t len) noexcept { return {str, len}; diff --git a/tests/StlTests.cpp b/tests/StlTests.cpp index 4ae7678e..120c0d61 100644 --- a/tests/StlTests.cpp +++ b/tests/StlTests.cpp @@ -128,7 +128,7 @@ TEST_CASE("StlTests::TestZStringView", "[stl][zstring_view]") TEST_CASE("StlTests::TestZWStringView literal", "[stl][zwstring_view]") { - + SECTION("Literal creates correct zwstring_view") { auto str = L"Hello, world!"_zv; @@ -137,7 +137,22 @@ TEST_CASE("StlTests::TestZWStringView literal", "[stl][zwstring_view]") REQUIRE(str[12] == L'!'); } } - + +TEST_CASE("StlTests::TestBSTR literal", "[stl][bstr]") +{ +#if __WI_LIBCPP_STD_VER >= 20 + SECTION("Literal creates a valid BSTR") + { + const auto literal = L"foo"_bst; + const BSTR value = literal; + REQUIRE(value != nullptr); + REQUIRE(SysStringLen(value) == 3); + REQUIRE(SysStringLen(L"zot"_bst) == 3); + REQUIRE(std::wstring_view(value) == L"foo"); + } +#endif +} + TEST_CASE("StlTests::TestZStringView literal", "[stl][zstring_view]") { From eb164ceeeb9a0e9b3baf0afaddba095ecb6ee7e9 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Fri, 3 Jul 2026 11:05:41 -0700 Subject: [PATCH 2/3] PR feedback --- include/wil/stl.h | 24 ++++++++++++------------ tests/StlTests.cpp | 9 +++++++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/include/wil/stl.h b/include/wil/stl.h index 4d085da5..1b4f02a9 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -268,11 +268,11 @@ inline namespace literals wchar_t value[N]; constexpr wchar_literal_storage(const wchar_t (&str)[N]) WI_NOEXCEPT { - for (std::size_t i = 0; i < N; ++i) - { - value[i] = str[i]; - } + std::copy_n(str, N, value); } + + static const std::size_t byte_size = N * sizeof(wchar_t); // includes the trailing L'\0' + static const std::size_t length = N - 1; // excludes the trailing L'\0' }; template @@ -285,13 +285,13 @@ inline namespace literals unsigned long m_byte_length; wchar_t m_data[N]; - constexpr bstr_literal_t(const wchar_t (&text)[N]) WI_NOEXCEPT : - m_byte_length(static_cast((N - 1) * sizeof(wchar_t))), m_data{} + constexpr bstr_literal_t(const wchar_literal_storage& text) WI_NOEXCEPT : + m_byte_length(text.byte_size - sizeof(wchar_t)), + m_data{} { - for (std::size_t i = 0; i < N; ++i) - { - m_data[i] = text[i]; - } + static_assert(sizeof(m_byte_length) == 4, "BSTR size must be 32 bits"); + static_assert(offsetof(bstr_literal_t, m_data) == sizeof(m_byte_length), "BSTR data must immediately follow the length prefix"); + std::copy_n(text.value, N, m_data); } WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT @@ -301,9 +301,9 @@ inline namespace literals }; template - WI_NODISCARD constexpr auto operator""_bst() WI_NOEXCEPT + WI_NODISCARD constexpr auto operator""_bstr() WI_NOEXCEPT { - return bstr_literal_t{Lit.value}; + return bstr_literal_t{Lit}; } #endif // __WI_LIBCPP_STD_VER >= 20 diff --git a/tests/StlTests.cpp b/tests/StlTests.cpp index 120c0d61..88b7c02f 100644 --- a/tests/StlTests.cpp +++ b/tests/StlTests.cpp @@ -143,12 +143,17 @@ TEST_CASE("StlTests::TestBSTR literal", "[stl][bstr]") #if __WI_LIBCPP_STD_VER >= 20 SECTION("Literal creates a valid BSTR") { - const auto literal = L"foo"_bst; + const auto literal = L"foo"_bstr; const BSTR value = literal; REQUIRE(value != nullptr); REQUIRE(SysStringLen(value) == 3); - REQUIRE(SysStringLen(L"zot"_bst) == 3); + REQUIRE(SysStringLen(L"zot"_bstr) == 3); REQUIRE(std::wstring_view(value) == L"foo"); + + constexpr auto empty_literal = L""_bstr; + REQUIRE(SysStringLen(empty_literal) == 0); + REQUIRE(empty_literal != nullptr); + REQUIRE(wcslen(empty_literal) == 0); } #endif } From da5eb7430ec0b863eef7cdda6ed8b333b5bf2aea Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Fri, 3 Jul 2026 11:12:18 -0700 Subject: [PATCH 3/3] Formatting --- include/wil/stl.h | 8 +++----- tests/StlTests.cpp | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/include/wil/stl.h b/include/wil/stl.h index 1b4f02a9..9604a4b4 100644 --- a/include/wil/stl.h +++ b/include/wil/stl.h @@ -272,7 +272,7 @@ inline namespace literals } static const std::size_t byte_size = N * sizeof(wchar_t); // includes the trailing L'\0' - static const std::size_t length = N - 1; // excludes the trailing L'\0' + static const std::size_t length = N - 1; // excludes the trailing L'\0' }; template @@ -285,9 +285,7 @@ inline namespace literals unsigned long m_byte_length; wchar_t m_data[N]; - constexpr bstr_literal_t(const wchar_literal_storage& text) WI_NOEXCEPT : - m_byte_length(text.byte_size - sizeof(wchar_t)), - m_data{} + constexpr bstr_literal_t(const wchar_literal_storage& text) WI_NOEXCEPT : m_byte_length(text.byte_size - sizeof(wchar_t)), m_data{} { static_assert(sizeof(m_byte_length) == 4, "BSTR size must be 32 bits"); static_assert(offsetof(bstr_literal_t, m_data) == sizeof(m_byte_length), "BSTR data must immediately follow the length prefix"); @@ -312,7 +310,7 @@ inline namespace literals { return {str, len}; } - + constexpr zwstring_view operator""_zv(const wchar_t* str, std::size_t len) noexcept { return {str, len}; diff --git a/tests/StlTests.cpp b/tests/StlTests.cpp index 88b7c02f..2f655668 100644 --- a/tests/StlTests.cpp +++ b/tests/StlTests.cpp @@ -128,7 +128,7 @@ TEST_CASE("StlTests::TestZStringView", "[stl][zstring_view]") TEST_CASE("StlTests::TestZWStringView literal", "[stl][zwstring_view]") { - + SECTION("Literal creates correct zwstring_view") { auto str = L"Hello, world!"_zv; @@ -137,7 +137,7 @@ TEST_CASE("StlTests::TestZWStringView literal", "[stl][zwstring_view]") REQUIRE(str[12] == L'!'); } } - + TEST_CASE("StlTests::TestBSTR literal", "[stl][bstr]") { #if __WI_LIBCPP_STD_VER >= 20 @@ -157,7 +157,7 @@ TEST_CASE("StlTests::TestBSTR literal", "[stl][bstr]") } #endif } - + TEST_CASE("StlTests::TestZStringView literal", "[stl][zstring_view]") {