Add _bst user-defined literal for BSTR-shaped wide strings#647
Conversation
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>
|
|
||
| WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT | ||
| { | ||
| return const_cast<wchar_t*>(&m_data[0]); |
There was a problem hiding this comment.
Do you need to guard against N=0? In that case this is getting the address of a zero-size buffer.
There was a problem hiding this comment.
Zero sized arrays are not valid in C++
| template <wchar_literal_storage Lit> | ||
| WI_NODISCARD constexpr auto operator""_bst() WI_NOEXCEPT | ||
| { | ||
| return bstr_literal_t<sizeof(Lit.value) / sizeof(wchar_t)>{Lit.value}; |
There was a problem hiding this comment.
Add something like:
static constexpr const std::size_t size = N;or a (constexpr) size() method to wchar_literal_storage so you don't need the manual length calculation
| #if __WI_LIBCPP_STD_VER >= 20 | ||
| SECTION("Literal creates a valid BSTR") | ||
| { | ||
| const auto literal = L"foo"_bst; |
There was a problem hiding this comment.
Should also include a constexpr validation
| struct bstr_literal_t | ||
| { | ||
| unsigned long m_byte_length; | ||
| wchar_t m_data[N]; |
There was a problem hiding this comment.
Consider: something like static_assert(offsetof(m_data) == 4)
| }; | ||
|
|
||
| template <wchar_literal_storage Lit> | ||
| WI_NODISCARD constexpr auto operator""_bst() WI_NOEXCEPT |
There was a problem hiding this comment.
My personal expectation is that this would be _bstr, but I don't use BSTRs enough to know how common of an "abbreviation" this is.
There was a problem hiding this comment.
One thing worth mentioning is that "bst" could stand for "binary search tree" (not that that would make much sense for a user defined literal)
| Use(L"foo"_bst); | ||
| */ | ||
| template <std::size_t N> | ||
| struct wchar_literal_storage |
There was a problem hiding this comment.
Given that the typical usage will be something like using namespace wil::literals, it's probably best to keep these types outside of the literals namespace. (You probably also don't want to drop them in a wil::literals::details namespace, because that may then interfere with a consumer's details namespace).
I gave it a try and it seems as though there's no "gotchas" for moving the type definitions out: https://godbolt.org/z/enaKvGs4M. Note that I've kept bstr_literal_t "public" so that its name can be uttered without acknowledging the details namespace.
| std::copy_n(text.value, N, m_data); | ||
| } | ||
|
|
||
| WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT |
There was a problem hiding this comment.
Right now this has a minor foot-gun. E.g. the following will compile, but has UB since it refers to a buffer that is outside its lifetime:
BSTR foo = L"foo"_bstr;One could argue this usage is unlikely - and I'd tend to agree - however I could see someone unknowingly running into this when assigning to member variables, fields of a struct, etc.
I can't think of any good standard ways to fix this without also breaking legitimate uses of r-values, however the non-standard lifetimebound attribute should at least be able to trigger a warning. E.g. somewhere near WI_NODISCARD and other definitions:
#if defined(__has_cpp_attribute)
# if __has_cpp_attribute(msvc::lifetimebound)
# define WI_LIFETIMEBOUND [[msvc::lifetimebound]]
# elif __has_cpp_attribute(clang::lifetimebound)
# define WI_LIFETIMEBOUND [[clang::lifetimebound]]
# elif __has_cpp_attribute(gnu::lifetimebound)
# define WI_LIFETIMEBOUND [[gnu::lifetimebound]]
# endif
#endif
#ifndef WI_LIFETIMEBOUND
# define WI_LIFETIMEBOUND
#endifAnd then:
WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT WI_LIFETIMEBOUND
{
return const_cast<wchar_t*>(&m_data[0]);
}Example that has the warning and an ASan failure: https://godbolt.org/z/qf9qxWfzf
Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>
Reduce global initializers and intermediate allocations with a
BSTR-compatible string literal.Instead, literals:
(Thanks Copilot!)