Skip to content

Windows: clang + MSVC STL 14.51 rejects std::find over a 24-byte trivially-comparable type (_Find_vectorized 'unexpected size') #609

Description

@Sunrisepeak

Filing this as a note for later, not a request — the defect is in the toolchain combination, not in mcpp. But mcpp is what puts that combination together on Windows, so this is where a downstream hitting it will look.

What happens

mcpp builds Windows with clang targeting x86_64-pc-windows-msvc — the MSVC ABI, the MSVC STL, and clang as the front end. On a runner image carrying MSVC STL 14.51 (Visual Studio 18), compiling a translation unit that calls std::find over a 24-byte type fails inside the standard library:

xutility:320:23: error: static assertion failed: unexpected size
  320 |         static_assert(false, "unexpected size");
xutility:6542:49: note: in instantiation of function template specialization
  'std::_Find_vectorized<const huxerui::detail::NodeExtensionHandle,
                         huxerui::detail::NodeExtensionHandle>' requested here
 6542 |    _Result = _STD _Find_vectorized(_First_ptr, _Last_ptr, _Val);

The element type is unremarkable:

struct NodeExtensionHandle {
  std::uint64_t             node_identity   = 0;   // 8
  std::size_t               extension_index = 0;   // 8
  const ModifierDescriptor* descriptor      = nullptr;  // 8
  bool operator==(const NodeExtensionHandle&) const = default;
};

24 bytes, no padding, trivially copyable, defaulted operator==.

Why it happens

MSVC STL's vectorized std::find is guarded by a trait deciding whether the element type can be compared bitwise. That guard admits this type under clang, and the helper it then dispatches to implements the 1/2/4/8-byte cases and static_asserts on everything else. Guard and implementation disagree about what "vectorizable" means, and only clang is present to notice — MSVC's own front end does not take this path.

It is version-specific, and that is the useful part

image MSVC STL same source, same clang
windows-2022 14.3x compiles
windows-latest (VS 18) 14.51 static_assert failure

So it is not the package's code and not a descriptor problem — it is this STL release against clang.

Blast radius, measured

On the mcpp-index shard where this surfaced, 13 of 14 members compiled fine on the 14.51 image; one did not. So it is narrow — it only bites code that instantiates std::find over a trivially-comparable type larger than 8 bytes. The narrowness is what makes it awkward: on a rolling image label, the set of affected packages changes underneath you, and the failure shows up attributed to whichever descriptor happened to change that week.

What was done downstream

mcpp-index pinned its Windows leg from windows-latest to windows-2022 (mcpplibs/mcpp-index#385), which is also what the affected project's own mcpp CI already does. That is a workaround, not a fix, and the pin should be lifted once clang and MSVC STL agree again.

Why it is filed here

Nothing for mcpp to change today. But anyone who hits this will hit it through mcpp build on Windows and will reasonably start here, and the error points into <xutility> with no hint that the STL version is the variable. A note in this tracker is the cheapest way to shorten that search. Feel free to close or relabel as upstream-tracking.

Activity

  1. Sunrisepeak commented on Sep 11, 2026

    @Sunrisepeak
    MemberAuthor

    Traced to the source. It is a known, open MSVC STL regression — microsoft/STL#6294, filed 2026-05-20, labelled bug, still open — so this note is now just a signpost. Nothing for mcpp to fix, but the chain is worth having here because the error text points into <xutility> with no hint of where the variable actually is.

    The chain, from microsoft/STL's own source

    The admitting guard is clang-only, and inert under MSVC (stl/inc/xutility):

    #ifdef __clang__
    template <class _Elem1, class _Elem2>
    constexpr bool _Is_same_and_builtin_trivially_equality_comparable =
        is_same_v<_Elem1, _Elem2> && __is_trivially_equality_comparable(_Elem1);
    #else
    template <class _Elem1, class _Elem2>
    constexpr bool _Is_same_and_builtin_trivially_equality_comparable = false;
    #endif

    It feeds _Vector_alg_in_find_is_safe, which checks contiguity, volatility and element compatibility — and no upper size bound. The dispatch site then adds only _Is_sized && _VECTORIZED_FIND for elements that are not 1 or 2 bytes. What receives the call implements four sizes:

    template <class _Ty, class _TVal>
    _Ty* _Find_vectorized(_Ty* const _First, _Ty* const _Last, const _TVal _Val) noexcept {
        if constexpr (sizeof(_Ty) == 1) { ... }
        else if constexpr (sizeof(_Ty) == 2) { ... }
        ...
        else { static_assert(false, "unexpected size"); }
    }

    So the guard says "bitwise-comparable, let it through" and the implementation says "I only do 1/2/4/8". The disagreement is unreachable under MSVC — that branch is hardcoded false — which is why it shipped.

    #6294 identifies the regression as coming from microsoft/STL#5479 (and PR #5767), the change that added the clang path to optimize more cases.

    Neither side's newer version helps

    Checked rather than assumed.

    MSVC STL main today still has it: no size exclusion in _Vector_alg_in_find_is_safe, and the #ifdef __clang__ block unchanged.

    clang is not the variable either. The builtin behaves the same across versions — measured locally with a 24-byte struct in both forms:

    clang 22.1.8   sizeof = 24   defaulted operator==   __is_trivially_equality_comparable = 1
    clang 22.1.8   sizeof = 24   explicit  operator==   __is_trivially_equality_comparable = 0
    

    Same answers from an LLVM 20-based compiler. So upgrading the toolchain does not move this, and clang is answering its own builtin correctly — a defaulted == on a padding-free struct is bitwise-comparable. The bug is that the STL treats that as a licence to vectorize at any size.

    What actually avoids it

    Three levers, and only one of them is available to a downstream today:

    level action owner status
    standard library exclude sizes other than 1/2/4/8 from the guard microsoft/STL #6294, open, no timeline
    application give the type an explicit operator== instead of = default the package one line, works now
    CI pin image + MSVC toolset mcpp-index done, mcpplibs/mcpp-index#388

    The middle row is the practical one and is confirmed by the table above: the trait flips to 0 and the type stops being eligible for the vectorized path. #6294's own repro shows the same — a hand-written operator== compiles, the defaulted one does not, for otherwise identical structs.

    A CI pin only protects CI. Anyone building with clang against a current MSVC STL still hits this, which is the part worth knowing before reaching for a toolchain pin as the fix.

  2. Sunrisepeak commented on Sep 11, 2026

    @Sunrisepeak
    MemberAuthor

    Documented in mcpp 2026.9.12.2 (#619). mcpp itself has no defect here; microsoft/STL#6294 is open upstream.

    mcpp is the tool that assembles clang with the MSVC STL, so docs/20 (and its Chinese copy) gains a "Known Toolchain Hazard" section stating:

    • the error text;
    • the affected STL release (14.51);
    • the upstream issue;
    • the two workarounds measured downstream: an explicit operator== on the element type, or an older runner image.

    Closing as documented upstream tracking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions