Skip to content

Add _bst user-defined literal for BSTR-shaped wide strings#647

Open
jonwis wants to merge 5 commits into
microsoft:masterfrom
jonwis:jonwis/static-bstrs-2
Open

Add _bst user-defined literal for BSTR-shaped wide strings#647
jonwis wants to merge 5 commits into
microsoft:masterfrom
jonwis:jonwis/static-bstrs-2

Conversation

@jonwis

@jonwis jonwis commented Jun 27, 2026

Copy link
Copy Markdown
Member

Reduce global initializers and intermediate allocations with a BSTR-compatible string literal.

// Unnecessary globals - 
const wil::unique_bstr g_bst(SysAllocString(L"kittens"));
const auto g_bst2 { wil::make_bstr_failfast(L"puppies"); };

HRESULT UseBstr(BSTR bst);
void call_bstr_function() {
    UseBstr(g_bst.get());

    // Unnecessary local allocation
    auto propName = wil::make_bstr_failfast(L"waffleCount");
    UseBstr(propName.get());

    // !!super mega ultra danger!! - not passing a real bstr...
    UseBstr(L"kablooie");
}

Instead, literals:

const auto g_bst = L"kittens"_bst;
const auto g_bst2 = L"puppies"_bst;

void call_bstr_function() {
    UseBstr(g_bst);
    UseBstr(L"waffleCount"_bst);
}

(Thanks Copilot!)

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>
Comment thread include/wil/stl.h

WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT
{
return const_cast<wchar_t*>(&m_data[0]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to guard against N=0? In that case this is getting the address of a zero-size buffer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero sized arrays are not valid in C++

Comment thread include/wil/stl.h Outdated
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};

@dunhor dunhor Jul 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread tests/StlTests.cpp Outdated
#if __WI_LIBCPP_STD_VER >= 20
SECTION("Literal creates a valid BSTR")
{
const auto literal = L"foo"_bst;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also include a constexpr validation

Comment thread include/wil/stl.h
struct bstr_literal_t
{
unsigned long m_byte_length;
wchar_t m_data[N];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider: something like static_assert(offsetof(m_data) == 4)

Comment thread include/wil/stl.h Outdated
};

template <wchar_literal_storage Lit>
WI_NODISCARD constexpr auto operator""_bst() WI_NOEXCEPT

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Comment thread include/wil/stl.h Outdated
Comment thread include/wil/stl.h
Use(L"foo"_bst);
*/
template <std::size_t N>
struct wchar_literal_storage

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread include/wil/stl.h
std::copy_n(text.value, N, m_data);
}

WI_NODISCARD constexpr operator BSTR() const WI_NOEXCEPT

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
#endif

And 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants