# Build fix: remove `boost_system` from CMake components

`Boost.System` became header-only in Boost 1.69. Boost 1.90.0 no longer ships a compiled library or CMake config for it, so `find_package(Boost REQUIRED COMPONENTS system ...)` fails.

## Fix

`cmake/CMakeLists.txt`:

```diff
-    set(BOOST_COMPONENTS system program_options regex thread date_time iostreams)
+    set(BOOST_COMPONENTS program_options regex thread date_time iostreams)
```

The headers are still available; no linking is needed.

# Build fix: add `template` keyword for dependent template member calls

Calling a template member function through a dependent expression (e.g. `storage->has_attribute_value<T>(...)` where `storage`'s type depends on a template parameter) requires the `template` keyword to disambiguate from a less-than comparison.

## Error

```
src/ifcparse/IfcParse.cpp:1856:67: error: expected primary-expression before '>' token
 1856 |                     if (storage->has_attribute_value<express::Base>(attr_index)) {
      |                                                                   ^
```

Six identical errors at lines 1856, 1865, 1896, 1905, 1934, 1943.

## Fix

`src/ifcparse/IfcParse.cpp`:

```diff
-storage->has_attribute_value<express::Base>(attr_index)
+storage->template has_attribute_value<express::Base>(attr_index)

-storage->has_attribute_value<Blank>(attr_index)
+storage->template has_attribute_value<Blank>(attr_index)
```

Applied at all six call sites in `in_memory_file_storage::read_from_stream`.

# Linker fix: missing explicit template instantiations for `InstanceStreamer`

`InstanceStreamer` is a class template with methods defined in `IfcParse.cpp`, not the header. Without explicit instantiations, the linker can't find the symbols when the SWIG wrapper loads.

## Error

```
ImportError: undefined symbol: _ZN8IfcParse16InstanceStreamerINS_10FileReaderINS_14FullBufferImplEEEEC1EPS3_PNS_7IfcFileE
  (IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(FileReader<FullBufferImpl>*, IfcFile*))
```

## Fix

Cannot use `template class InstanceStreamer<...>` because some constructors have `static_assert` guards that reject certain reader types. Instead, instantiate each member function individually per reader type, only including the constructors valid for that type.

`src/ifcparse/IfcParse.cpp` (after the last `InstanceStreamer` method definition):

```cpp
// FullBufferImpl
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(IfcParse::IfcFile*);
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(const std::string&, bool, IfcParse::IfcFile*);
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(void*, int, IfcParse::IfcFile*);
template IfcParse::InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(FileReader<FullBufferImpl>*, IfcParse::IfcFile*);
// ... plus ensure_header, initialize_header, hasSemicolon, semicolonCount,
//     pushPage, bypassTypes, readInstance

// PushedSequentialImpl — same pattern, different valid constructors

// MMapFileReader (ifdef USE_MMAP) — same pattern
```

# Linker fix: `FullBufferImpl` missing buffer constructor

SWIG's `stream_from_string` calls `InstanceStreamer<FileReader<FullBufferImpl>>(void*, int, IfcFile*)`, but the `(void*, int)` constructor previously hit a `static_assert` for `FullBufferImpl` — it only allowed `PushedSequentialImpl`.

## Error

```
ImportError: undefined symbol: _ZN8IfcParse16InstanceStreamerINS_10FileReaderINS_14FullBufferImplEEEEC1EPviPNS_7IfcFileE
  (InstanceStreamer<FileReader<FullBufferImpl>>::InstanceStreamer(void*, int, IfcFile*))
```

## Fix

Three changes to make `FullBufferImpl` support buffer-based and default construction:

`src/ifcparse/FileReader.h` — add buffer constructor to `FullBufferImpl`:

```diff
 class IFC_PARSE_API FullBufferImpl {
 public:
     explicit FullBufferImpl(const std::string& fn);
+    FullBufferImpl(void* data, size_t length);
```

`src/ifcparse/FileReader.h` — add `FileReader(void*, size_t)` forwarding constructor:

```diff
+    FileReader(void* data, size_t length)
+        : cursor_(0) {
+        if constexpr (std::is_same_v<Impl, FullBufferImpl>) {
+            impl_ = std::make_shared<Impl>(data, length);
+        } else {
+            static_assert(...);
+        }
+    }
```

`src/ifcparse/FileReader.cpp` — implement the constructor:

```cpp
FullBufferImpl::FullBufferImpl(void* data, size_t length)
    : buf_(static_cast<char*>(data), static_cast<char*>(data) + length)
    , size_(length) {
}
```

`src/ifcparse/IfcParse.cpp` — extend the two `InstanceStreamer` constructors to accept `FullBufferImpl`:

```diff
 // InstanceStreamer(IfcFile*):
+    } else if constexpr (std::is_same_v<Reader, FileReader<FullBufferImpl>>) {
+        owned_stream_ = std::make_unique<Reader>(nullptr, (size_t)0);

 // InstanceStreamer(void*, int, IfcFile*):
+    } else if constexpr (std::is_same_v<Reader, FileReader<FullBufferImpl>>) {
+        owned_stream_ = std::make_unique<Reader>(data, (size_t)length);
```

# Runtime fix: segfault in `parse_context::push()` due to vector reallocation

`parse_context_pool` stores nodes in a `std::vector<parse_context>`. During parsing, `load()` takes a `parse_context&` parameter and calls `context.push()`, which calls `pool_->make()`. If the pool's vector reallocates (via `emplace_back`), all existing references into the vector — including the `context` reference held by the caller — become dangling. Subsequent access through the dangling reference causes a segfault.

Triggered by larger IFC files (e.g. `ISSUE_159_kleine_Wohnung_R22.ifc`, 9.5 MB) that cause enough pool growth to trigger reallocation.

## Error

```
Thread 1 received signal SIGSEGV, Segmentation fault.
0x... in IfcParse::parse_context::push()
  #1  in_memory_file_storage::load(...)   // context& is dangling after reallocation
  #2  in_memory_file_storage::load(...)   // parent call
  #3  InstanceStreamer::readInstance()
```

## Fix

`src/ifcparse/storage.h` — change the pool container from `std::vector` to `std::deque`, which does not invalidate references on `push_back`/`emplace_back`:

```diff
+#include <deque>

 struct parse_context_pool {
-    std::vector<parse_context> nodes_;
+    std::deque<parse_context> nodes_;
```

# Runtime fix: `express::Base` comparison operators throw on null/expired instances

`express::Base::operator<` and `operator==` called `data()`, which throws `std::runtime_error("Trying to access deleted instance reference")` when the internal `weak_ptr` is expired. A default-constructed `express::Base` (the value-type equivalent of a null pointer) always has an expired `weak_ptr`.

## Why this model triggers it

The bug requires two conditions to coincide:

1. A representation is shared by **more than one product** (via `IfcRepresentationMap` / `IfcMappedItem`).
2. At least one of those products has **no material association**, so `get_single_material_association()` returns `express::Base{}` (the null equivalent).

In `advanced_model.ifc`, Body representations like `#449` (Body/Brep) have a single `IfcRepresentationMap` (`#453`) with 13 `IfcMappedItem` usages, meaning 13 products share the geometry. Some of those products (e.g. `IfcFlowTerminal` instances) have no `IfcRelAssociatesMaterial`, so `get_single_material_association` returns `express::Base{}`.

Smaller or simpler models don't hit this because either:
- Every representation maps to only 1 product → `reuse_ok_` short-circuits at `products.size() == 1` before reaching the material check.
- Every product has a material association → no null `express::Base` is ever inserted into the set.

## Exact call sequence

```
Iterator::initialize()
  try {
    mapping::get_representations(reps, filters_)
      addRepresentationsFromDefaultContexts(representations)
        → collects reps from subcontexts in order:
          Axis (#115): 143 reps
          Body (#117): 7550 reps
          FootPrint (#119): 12 reps

      for (auto representation : representations):

        ── Axis reps (indices 0–142) ──────────────────────────
        products_represented_by(rep, rmap)
          → OfProductRepresentation: 1 product each
        filter_products(products, filters)   → 1 product
        reuse_ok_(ifcproducts)
          → products.size() == 1 → return true  ← SHORT-CIRCUIT, no material check
        representation_mapped_to(rep)        → null (no MappedItem)
        → task created.  143 tasks accumulated.

        ── First Body rep #449 (Body/Brep) ────────────────────
        products_represented_by(#449, rmap)
          → OfProductRepresentation: empty
          → RepresentationMap: 1 map (#453)
          → MapUsage: 13 MappedItems → traces through to 13 IfcProducts
        filter_products(products, filters)   → 13 products
        reuse_ok_(ifcproducts)               ← CRASH HERE
          → products.size() == 1?  NO (13 products)
          → for each product:
              find_openings(product)          → OK
              get_single_material_association(product)
                → some products have no IfcRelAssociatesMaterial
                → returns express::Base{}     (expired weak_ptr)
              associated_single_materials.insert(result)
                → std::set::insert calls operator<
                → operator< calls data()
                → data() calls data_.lock() → expired → THROWS
                  "Trying to access deleted instance reference"

  } catch (const std::exception& e) {
    Logger::Error(e)       ← exception caught here, get_representations aborted
  }

  → reps contains only the 143 Axis tasks created before the throw
  → all 143 Axis reps have Curve2D geometry → map(representation) returns null
  → no valid elements produced → initialize() returns false
```

In the old pointer-based code, `reuse_ok_` used `std::set<const IfcUtil::IfcBaseEntity*>` and `get_single_material_association` returned `nullptr`. Inserting `nullptr` into a `std::set<T*>` is a plain pointer comparison — no dereference, no throw. The refactoring to `std::set<express::Base>` changed the comparison from pointer comparison to `express::Base::operator<`, which unconditionally dereferences through `data()`.

## Error

```
[Error] Trying to access deleted instance reference
[Notice] Created 143 tasks for 143 products    ← only Axis reps; all Body reps lost
initialize() returned: False
```

## Fix

`src/ifcparse/express.h` — use `weak_ptr::lock().get()` instead of `data()` so that expired pointers compare as `nullptr` (matching old raw-pointer semantics):

```diff
     bool operator<(const Base& other) const {
-        return data() < other.data();
+        auto a = data_.lock();
+        auto b = other.data_.lock();
+        return a.get() < b.get();
     }

     bool operator==(const Base& other) const {
-        return data() == other.data();
+        auto a = data_.lock();
+        auto b = other.data_.lock();
+        return a.get() == b.get();
     }
```
