Rolling table of C++ features and how they round-trip through the K/N boundary
under v2. Rows land in batches as we sign off on the shape, then get scaffolded
into feature-tests/ by the scaffold-feature-row skill.
Columns. Status is set by the test harness, not by hand. Test cases is
the contract β each bullet becomes an @Test in Cases.kt.
| Status legend | |
|---|---|
| βͺ | row signed off, scaffolding not yet authored |
| π’ | scaffolded and all cases pass |
| π‘ | scaffolded; some cases pass with documented workaround |
| π΄ | scaffolded; cases fail and need engineering |
| ID | Feature | C++ sig | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| PR-bool | bool round-trip |
bool negate(bool b); |
assertFalse(negate(true)) |
β’ call with true / false, assert returnβ’ repeat in loop, assert C++ inspector counter advances |
π’ | warm-up; cinterop handles bool natively (needs #include <stdbool.h> for C-mode header parse) |
| PR-int-rt | int arg + return |
int addOne(int x); |
assertEq(8, addOne(7)) |
β’ boundaries: 0, -1, 1, INT_MIN, INT_MAX β’ negative round-trip β’ Kotlin asserts C++ inspector lastArg() matches |
π’ | reference primitive case; INT_MAX boundary only checks input propagation, not +1 return (signed overflow UB) |
| PR-int-out | int out via ptr |
void produce(int* out); |
val r = intRef(); produce(r); assertEq(42, r.value) |
β’ Kotlin asserts r.value == 42 post-callβ’ inspector reports producedCount() incrementedβ’ null-pointer path: Kotlin passes null, assert C++ inspector flagged it |
π’ | first out-param case; tests use memScoped { alloc<IntVar>() } + .ptr directly (no intRef() facade yet) |
| PR-char | char round-trip |
char echo(char c); |
assertEq('A'.code.toByte(), echo(...)) |
β’ round-trip 'A', 0, 0x7Fβ’ inspector lastArg() matches |
π’ | signedness platform-dependent; treat as Byte |
| PR-uchar | unsigned char round-trip |
unsigned char echo(unsigned char c); |
Byte/UByte |
β’ round-trip 0u, 255u, midβ’ inspector matches |
π’ | full 0β255 range, no sign confusion |
| PR-short | short round-trip |
short echo(short x); |
Short |
β’ boundaries 0, -1, SHRT_MIN, SHRT_MAXβ’ inspector matches |
π’ | |
| PR-ushort | unsigned short round-trip |
unsigned short echo(unsigned short x); |
UShort |
β’ boundaries 0u, 65535u, midβ’ inspector matches |
π’ | |
| PR-uint | unsigned int round-trip |
unsigned int echo(unsigned int x); |
UInt |
β’ boundaries 0u, UINT_MAX, midβ’ inspector matches |
π’ | sign-bit-set value (0xFFFFFFFFu) survives |
| PR-long | long round-trip |
long echo(long x); |
Long (LP64) |
β’ boundaries 0, -1, LONG_MIN, LONG_MAXβ’ inspector matches |
π’ | Linux/macOS x64 = 64-bit; Windows would be 32-bit (out of scope) |
| PR-ulong | unsigned long round-trip |
unsigned long echo(unsigned long x); |
ULong |
β’ boundaries 0u, ULONG_MAX, midβ’ inspector matches |
π’ | |
| PR-longlong | long long round-trip |
long long echo(long long x); |
Long |
β’ boundaries 0, -1, LLONG_MIN, LLONG_MAXβ’ inspector matches |
π’ | |
| PR-float | float round-trip |
float echo(float x); |
Float |
β’ round-trip 0f, -1.5f, 3.14fβ’ special: NaN/+Inf/-Infβ’ inspector matches |
π’ | NaN asserted via isNaN(), not == |
| PR-double | double round-trip |
double echo(double x); |
Double |
β’ round-trip 0.0, -1.5, PIβ’ special: NaN/+Inf/-Infβ’ inspector matches |
π’ | same NaN caveat |
| PR-size-t | size_t round-trip |
size_t echo(size_t x); |
platform.posix.size_t (= ULong LP64) |
β’ boundaries 0u, mid, ULONG_MAXβ’ inspector matches |
π’ | cinterop preserves the alias; krapper_gen collapses it to bare ULong (tracked, see Known issues) |
| PR-ptrdiff-t | ptrdiff_t round-trip |
ptrdiff_t echo(ptrdiff_t x); |
platform.posix.ptrdiff_t (= Long) |
β’ boundaries 0, -1, LONG_MIN, LONG_MAXβ’ inspector matches |
π’ | signed counterpart of size_t |
| PR-intptr | intptr_t / uintptr_t round-trip |
intptr_t echo(intptr_t x); |
platform.posix.intptr_t/uintptr_t (= Long/ULong) |
β’ intptr boundaries 0, -1, LONG_MIN, LONG_MAXβ’ uintptr boundaries 0u, mid, ULONG_MAXβ’ inspector matches |
π’ | integer-that-holds-a-pointer; handle-style APIs |
| PR-wchar | wchar_t round-trip |
wchar_t echo(wchar_t x); |
platform.posix.wchar_t (= Int, 4B Linux) |
β’ round-trip ASCII + astral 0x1F600β’ inspector matches |
π’ | Linux/macOS only: 4-byte wchar_t. Windows is 2-byte (UShort) and the astral case would not fit β revisit when a Windows target is in scope |
| PR-char16 | char16_t round-trip |
char16_t echo(char16_t x); |
UShort |
β’ round-trip ASCII + BMP 0x20AC β¬β’ inspector matches |
π’ | UTF-16 code unit |
| PR-char32 | char32_t round-trip |
char32_t echo(char32_t x); |
UInt |
β’ round-trip ASCII + astral 0x1F600 πβ’ inspector matches |
π’ | UTF-32 code point |
| PR-char8 | char8_t round-trip |
char8_t echo(char8_t c); |
UByte |
β’ round-trip 0u, 0xFFu |
βͺ | Deferred: C++20-only; harness compiles at -std=c++17 so it doesn't surface. Bump the standard to enable. No test yet (matrixReport will log it as unscaffolded). |
Group A is pure cinterop (testable under the current harness). Group B needs a
generator-backed harness mode (krapper_gen + compiler plugin wired into
:feature-tests) β those rows are signed off in shape but not yet scaffolded,
and their "Desired Kotlin" is pinned down when that harness lands.
| ID | Feature | C++ sig | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| ST-cstr-in | const char* arg in |
void take(const char* s); |
take("hello") |
β’ ASCII round-trip (inspector sees bytes + length) β’ empty string β’ UTF-8 2/3/4-byte byte-exact β’ embedded NUL truncates (documents the limitation) β’ null pointer flagged β’ OOB byte read |
π’ | Group A. cinterop maps const char* param to String? (UTF-8 convert) |
| ST-cstr-ret | const char* return (borrowed) |
const char* name(); |
name()?.toKString() |
β’ content decodes β’ non-null β’ pointer stable across calls β’ content stable when repeated |
π’ | Group A. borrowed static storage; caller never frees |
| ST-charbuf-out | fill caller buffer | size_t fill(char* buf, size_t n); |
allocArray<ByteVar>(n) β fill β toKString() |
β’ fills full payload + returns count β’ NUL-terminated β’ truncates without overrun β’ n==0 writes nothingβ’ payload-len helper |
π’ | Group A. classic C buffer-fill size/truncation contract |
| ST-wcstr | wide C string | const wchar_t* wname(); void wtake(const wchar_t*); |
wname()?.toKStringFromUtf32() |
β’ ASCII round-trip β’ BMP code point (β¬) β’ empty β’ OOB read β’ returned astral (π) decodes |
π’ | Group A. 4-byte wchar_t β CValuesRef<IntVar>?; built by hand (.wcstr is 2-byte) |
| ST-string-in | const std::string& arg in |
void take(const std::string& s); |
take(__Basic_string__Char("hi")) |
β’ construct from Kotlin String, pass by const ref β’ inspector size + bytes β’ empty / UTF-8 byte-exact / longer β’ embedded NUL truncates at construction |
π’ | Generator-backed (:featuregen). std::string built from Kotlin String via MemScope.__Basic_string__Char(String?); no size() on the binding (filtered) so inspector reads size. Embedded NUL truncates because the Stringβstd::string bridge goes through const char* |
| ST-string-rt | std::string full round-trip |
std::string echo(const std::string&); |
echo(s).c_str() |
β’ echo round-trip via c_str() β’ UTF-8 byte-exact β’ empty β’ constructed-empty reports empty |
π’ | return-by-value now placement-news into the Holder (krapper_gen ARG_CAST fix); see Known issues |
| ST-string-ret | std::string return by value |
std::string produce(); |
produce().c_str() |
β’ content matches β’ not empty β’ stable across calls |
π’ | ownership = scope-bounded (Holder freed with the MemScope); fixed by the same placement-new change |
| ST-stringview-in | std::string_view arg in |
void take(std::string_view sv); |
TBD | β’ content + length β’ lifetime (view must not outlive source) β’ empty view |
π΄ | C++ standard is now configurable (kplusplus { cppStandard = "c++17" }), which fixes the parse β but two blockers remain: krapper_gen doesn't wrap string_view as a type (the take method is silently dropped, like wstring), and bumping to c++17 separately drops std::string's const char* constructor. See Known issues |
| ST-wstring | std::wstring round-trip |
std::wstring echo(const std::wstring&); |
TBD | β’ ASCII + non-ASCII round-trip β’ size() matches |
π΄ | std::basic_string<wchar_t> is not wrapped. Methods taking/returning std::wstring are silently dropped from the generated binding (only the non-wstring members of WStringFeature survive) |
Deferred (strings): std::u16string / std::u32string β pair with the
deferred char8_t / Unicode-string work.
The v2 headline: cppVector<T>() written transparently, the compiler plugin
detecting the instantiation, krapper_gen generating the binding. Tested in
:featuregen (plugin applied + sync). The facade entry points
(cppVector/cppMap) live in package com.monkopedia.kplusplus β the plugin
recognizes them there (was hardcoded to the slice demo's package; now neutral,
anticipating a runtime lib).
| ID | Feature | C++ surface | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| CV-construct | empty vector | std::vector<int> |
cppVector<Int>() |
β’ size()==0 β’ empty()==true |
π’ | |
| CV-push-size | push + size | push_back, size |
v.push_back(7); v.size() |
β’ push N β size()==N β’ size grows one per push |
π’ | |
| CV-clear | empty out | clear() |
v.clear() |
β’ after clear size()==0, empty()==true | π’ | |
| CV-elem-double | non-Int element | std::vector<double> |
cppVector<Double>() |
β’ construct/push/size | π’ | element-type generality |
| CV-index-get | element out by index | operator[] / at |
v[0uL]?.getPointer(this)?.pointed?.value |
β’ read back pushed values β’ at() matches subscript |
π’ | accessors recovered by the reference-typedef fix; return a pointer to the element, deref to read |
| CV-front-back | front / back | front(), back() |
v.front()?.getPointer(this)?.pointed?.value |
β’ front==first, back==last | π’ | recovered by the same fix |
| CV-iterate | traverse | indexed read over size() | while (i < v.size()) { v[i]... } |
β’ sum + count over elements | π’ | index traversal via size() + subscript |
| CV-nested | vector of vector | std::vector<std::vector<int>> |
cppVector<Vector__Int>() |
β’ push inner vectors (built via cppVector<Int>())β’ outer size reflects β’ read inner vectors back ( outer[i] β inner size()/subscript) |
π’ | transitive instantiation (inner generated before outer). outer[i] wraps the inner vector's own pointer as a Vector__Int, so its size()/subscript read the inner ints β read-back verified (read_inner_vectors_back) |
| CV-elem-string | vector of std::string | std::vector<std::string> |
cppVector<Basic_string__Char>() |
β’ push std::strings β’ size reflects β’ read strings back ( v[i]/at/front/back β c_str()) |
π’ | needed the forcing-TU header fix (Known issues); v[i] wraps the element's own pointer as a Basic_string__Char, so c_str() decodes the stored bytes β read-back verified (read_strings_back_out) |
| CV-elem-class | vector of a user struct | std::vector<Point> |
cppVector<Point>() |
β’ Point binding fields (x/y) round-trip β’ push Points β’ size reflects β’ read elements back ( v[i]/at/front/back β Point fields) |
π’ | "user type in a container" via Point's @CppBinding("Point"). operator[]/at/front/back wrap the element's own pointer as a Point, so v[i]?.x/?.y read straight off the stored element β read-back now verified (read_points_back_out) |
Tested in :featuregen. Map's operator[] survives the resolver (returns
mapped_type& β int&), so subscript insert/read work. size()/count()/
erase(key) are now present too β they were dropped because their size_type
return is another dependent member typedef libclang left unexposed; the
size_type typedef fix recovers them (same family as the reference accessor
fix). The iterator-returning overloads (find, iterator-arg erase,
insert returning pair<iterator,bool>) remain dropped pending iterator
support.
| ID | Feature | C++ surface | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| CM-construct | empty map | std::map<int,int> |
cppMap<Int,Int>() |
β’ empty()==true | π’ | no size() on the binding, so construct asserted via empty() only |
| CM-insert | insert via subscript | m[k] = v |
m[k]?.getPointer(this)?.pointed?.value = v |
β’ subscript inserts; empty() flips false | π’ | operator[] default-inserts + returns a pointer to the mapped value; write through it |
| CM-get | read via subscript / at | v = m[k] |
m[k]?.getPointer(this)?.pointed?.value |
β’ read back inserted values β’ at() matches subscript |
π’ | pointer deref, same as vector elements |
| CM-clear | empty out | clear() |
m.clear() |
β’ clear β empty() true | π’ | |
| CM-value-double | non-Int value | std::map<int,double> |
cppMap<Int,Double>() |
β’ insert/read double values | π’ | value-type generality |
| CM-size | element count | size() |
m.size() |
β’ size==distinct keys β’ re-insert same key doesn't grow |
π’ | recovered by the size_type typedef fix |
| CM-count | membership | count(k) |
m.count(k) |
β’ 1 present / 0 absent | π’ | recovered by the size_type typedef fix |
| CM-erase | remove a key | erase(k) |
m.erase(k) |
β’ erase by key shrinks map + returns count removed β’ erase absent β 0 |
π’ | erase(key) returns size_type β recovered. The iterator-arg erase overloads are still dropped (iterators unwrapped), but the by-key one is what consumers want |
| ID | Feature | C++ | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| CP-pair | std::pair<A,B> |
cppPair<A,B>() |
p.first / p.second |
β’ first/second round-trip β’ fields independent |
π’ | first/second generate as var field accessors. pair's default ctor isn't wrapped, so the facade falls back to the _Holder factory (valid for trivially-constructible element types; flagged for non-trivial). Added cppPair to the plugin facade registry |
| CP-tuple | std::tuple<...> |
cppTuple<...>() |
β | β | π΄ | no binding generated at all β std::get<N> uses non-type template params + free functions krapper doesn't wrap. Deferred |
Now scaffolded with tests and wired into the plugin registry (cppSet/
cppUnorderedMap/cppUnorderedSet). The lookup/membership surface works across
all three; only real iteration (find/begin/end, range-for) is still π΄ β
it needs nested iterator-class wrapping (a genuinely deep, separate feature).
| ID | Feature | C++ | Desired Kotlin | Status | Notes |
|---|---|---|---|---|---|
| CS-set | std::set<T> |
cppSet<T>() |
insert/count/size/erase/clear |
π’ | usable: insert(7): Boolean (was-newly-inserted), count (membership), size, erase, clear. value_type reducer (set branch of assocTypedefElement) + a pair<iterator,bool>βbool rewrite (returnsPairSecond flag β .second). find/iterator-returning members dropped (iteration is a deep separate feature). CsSetTest |
| CU-map | std::unordered_map<K,V> |
cppUnorderedMap<K,V>() |
construct, at/operator[]/count/erase, size/clear/β¦ |
π’ | std::map parity for the lookup surface: a mappedTypedefElement reducer reconstructs unordered's collapsed key_typeβparam0 / mapped_typeβparam1 (libclang reports them as unresolvable typename _Hashtable::β¦ since unordered has no base of its own), so at/operator[]/count/erase resolve. Earlier crash (uncompilable initializer_list ctor) also fixed. find/insert/begin/end still dropped (need iterator wrapping + the real pair value_type). CuUnorderedMapTest |
| CU-set | std::unordered_set<T> |
cppUnorderedSet<T>() |
insert/count/size/erase/clear |
π’ | usable like std::set β the set-style value_type reducer + pair<iterator,bool>βbool rewrite landed for std::set also unblocked unordered_set (same shape). insert(7): Boolean, count, erase. find/iteration still π΄. CuUnorderedSetTest |
Probed by direct instantiation; not scaffolded. The typedef-reducer pattern that unblocked container accessors/size doesn't reach these β smart pointers route their member types through impl-class indirection that needs template-argument substitution, not just sibling-typedef reduction.
| ID | Feature | C++ | Desired Kotlin | Status | Notes |
|---|---|---|---|---|---|
| SP-unique | std::unique_ptr<T> |
cppUniquePtr<T>() |
get/release/pointer_reference (operator->) |
π’ partial | get/release/operator-> + the pointer-taking reset now resolve: a pointerTypedefElement reducer maps the pointer C++11 alias β _Tp* via sibling element_type, then the existing _Tpβconcrete engine substitutes. operator* (a dependent add_lvalue_reference<element_type>::type expr) and constructing a non-null unique_ptr from Kotlin (the unique_ptr(pointer) ctor) are follow-ups. SpUniquePtrTest |
| SP-shared | std::shared_ptr<T> |
cppSharedPtr<T>() |
β | π΄ | generates nothing β base-class resolution fails: shared_ptr β __shared_ptr<_Tp,_Lp> β __shared_ptr_access<...> carry an unsubstituted _Tp plus an unresolveable _Lp lock-policy (a non-type/enum template param). Needs base-class template-arg substitution and non-type-param handling β strictly deeper than unique_ptr. Same base-class gap blocks the unordered accessors |
| SP-weak | std::weak_ptr<T> |
cppWeakPtr<T>() |
β | π΄ | not probed; expected to share shared_ptr's blockers |
An enum-typed param/return surfaces as a synthesized Kotlin enum class with
the original named constants. krapper represents the C++ enum as a
WrappedEnumType carrying the real spelling, its underlying integer, AND its
constants (name + value). The Kotlin binding emits
enum class Color(val value: Int) { Red(0), Green(1), Blue(2); companion object { fun fromValue(v: Int): Color = entries.first { it.value == v } } } and methods
surface as Color β Color. The C/C++ boundary is unchanged (the wrapper's C
signature stays the underlying integer; the generated C++ casts (Color)c /
(int)call); the Kotlin edge converts arg.value in and Color.fromValue(int)
out. Tested in :featuregen.
| ID | Feature | C++ | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| EN-unscoped | unscoped enum E arg/return |
enum Direction { North, East, South, West } |
enum class Direction(val value: UInt) |
β’ Direction.fromValue/.valueβ’ turnRight cycles NβEβSβWβNβ’ toInt/fromInt round-trip |
π’ | Unsigned underlying β value: UInt. Even an unscoped enum needs the boundary cast (C++ won't implicitly convert integerβenum for an argument) |
| EN-scoped | scoped enum class E : int arg/return |
enum class Color : int { Red, Green, Blue } |
enum class Color(val value: Int) |
β’ Color.Red.value == 0, Color.fromValue(1) == Greenβ’ next cycles RedβGreenβBlueβRedβ’ toInt/fromInt round-trip |
π’ | Surfaces as a real Kotlin enum class with named constants; value is the : int underlying |
Implementation. WrappedEnumType (toString = enum spelling, cType/
kotlinType = underlying integer, isEnum = true); createForType builds it for
CXCursor_EnumDecl from clang_getEnumDeclIntegerType. Enum args resolve with
castMode = RAW_CAST (emits (E)(x)) and needsDereference = false; enum returns
use a new ReturnStyle.ENUM_RETURN that casts the result to the integer. isEnum
propagates through const/reference wrappers (a const E / const E& is still an
enum value; a pointer/array to one is not). canResolve short-circuits enums true.
Surfacing previously-dropped enum operators also required sanitizing the bitwise
operator symbols (& | ^ ~) in NameHandler.cleanupName β V8's free flag-enum
operators (std::operator&(E,E)) now generate valid C names.
Kotlin enum-class synthesis (the richer surface). WrappedEnumType also carries
the enum's constants (recovered from the CXCursor_EnumConstantDecl children via
clang_getEnumConstantDeclValue). When present, WrappedKotlinType maps the enum to
an EnumKotlinType (delegating its name/pkg to a same-named wrapper type, carrying
the constants), ResolvedKotlinType gains enumEntries/enumUnderlying, and
KotlinWriter.generateEnums emits one enum class file per distinct enum; reference
passes arg.value and generateReturn wraps results in fromValue(...). The C/C++
side is byte-identical to the integer-reduction version. Deferred: enum-typed fields
(handled but untested β no enum field in the probe), enum pointers/references, and
anonymous/duplicate-value enums (fromValue picks the first matching constant).
A plain (non-template) user class Widget exercised through :featuregen β the
systematic version of what Point/StringFeature/EnumFeature showed piecemeal.
All rows π’ (scaffolded by an orchestrated subagent; UcConstructTest,
UcFieldMethodTest, UcStaticOpTest).
| ID | Feature | C++ surface | Desired Kotlin | Test cases | Status | Notes |
|---|---|---|---|---|---|---|
| UC-ctor-default | default construction | Widget() |
with(Widget){ ms.Widget() } |
β’ construct; default fields (count==0, scale==0.0) | π’ | |
| UC-ctor-args | construct with args | Widget(int, double) |
ms.Widget__int_double(3, 1.5) |
β’ fields reflect args; int+double both round-trip | π’ | content-based factory name (overload-naming fix) |
| UC-ctor-overload | overload selection | default + (int,double) | both factories reachable | β’ both ctors callable from one binding | π’ | no brittle _-prefix β distinct names |
| UC-field-rw | public field read/write | int count; double scale; |
w.count / w.count = n |
β’ read both; write then read back; independent | π’ | var accessors |
| UC-method-void | void mutator | void reset() |
w.reset() |
β’ mutates state (fields zeroed), observable after | π’ | |
| UC-method-ret | value-returning method | int compute(int) const |
w.compute(5) |
β’ return reflects state+arg; arg round-trips | π’ | |
| UC-const-method | const method | double scaled() const |
w.scaled() |
β’ callable; does not mutate | π’ | const this wrapped |
| UC-static-method | static factory | static Widget make(int) |
with(Widget){ ms.make(7) } |
β’ returns a constructed Widget by value; fields correct | π’ | return-by-value (Holder placement-new) |
| UC-operator-eq | equality operator (hand-written) | bool operator==(const Widget&) const |
a.valueEquals(b) |
β’ value equality via valueEquals; Kotlin == is identity |
π’ | hand-written == β IDENTITY equals/hashCode + valueEquals (see the equals/hashCode note) |
| UC-operator-plus | arithmetic operator (by value) | Widget operator+(const Widget&) const |
a + b |
β’ sum combines fields; operands unchanged; independent result | π’ | maps to a real operator fun plus |
Note: operator mapping is asymmetric β operator+ becomes Kotlin operator fun plus
(a + b works). operator=='s mapping is gated on whether it is C++20 defaulted.
operator== β equals + hashCode, gated on defaulted-ness (BUILT π’). The mapping
depends on whether the C++ operator== is = default:
- Defaulted
==(bool operator==(const T&) const = default;) β the compiler guarantees a MEMBERWISE comparison over all members, so it maps to a real Kotlinequals(other: Any?)override (other is T && T_op_eq(ptr, other.ptr)) PAIRED with ahashCode()that folds the public fields (return 0when none). Equal objects have all members equal, so the fold agrees β the equals/hashCode contract is provably sound. - Hand-written
==β can't be statically classified as memberwise (it may compare only a subset of state), so binding it to Kotlinequalsagainst a field-fold hashCode would break the contract. Instead:equalsis IDENTITY on the backing pointer,hashCode()isptr.hashCode(), and the C++ comparison stays reachable asvalueEquals(other: T): Boolean(delegating to the same_op_eqC wrapper the delegatingequalswould have used).
(operator< β compareTo is now built: a single operator< synthesizes the whole
ordering β this<o β -1, o<this β 1, else 0 β and Kotlin derives >/>=/<=. See
OP-compare.)
A Vec2 value type with overloaded operators, scaffolded into :featuregen
(OpArithmeticTest, OpRelationalTest, OpAssignTest). krapper maps most
operators (see Operators.kt); the gaps are Vec2&-returning compound ops and
operator()/conversion operators.
| ID | C++ | Generated Kotlin | Status | Notes |
|---|---|---|---|---|
| OP-eq | operator== (hand-written) |
override fun equals (IDENTITY on ptr) + override fun hashCode (ptr.hashCode()) + valueEquals(o) |
π’ | Vec2's == is hand-written β Kotlin == is identity; the C++ value comparison is valueEquals; contract-sound. OpRelationalTest |
| OP-eq-defaulted | operator== = default |
override fun equals(other: Any?) (delegating) + override fun hashCode() (field-fold) |
π’ | a C++20 defaulted == is memberwise, so a == b runs the C++ == and the field-fold hashCode is sound (equal β equal hash). VecEq, OpRelationalTest |
| OP-neq | operator!= |
infix fun neq(o): Boolean |
π’ | |
| OP-plus | operator+ |
operator fun plus(o): Vec2 |
π’ | real a + b; return-by-value via Holder |
| OP-minus | operator- (binary) |
operator fun minus(o): Vec2 |
π’ | |
| OP-scale | operator*(double) |
operator fun times(scalar: Double): Vec2 |
π’ | heterogeneous rhs resolves cleanly |
| OP-index | operator[](int) |
operator fun get(i: Int): Double |
π’ | real v[i]; rhs is Int (the C++ int), unlike vector's ULong |
| OP-compare | operator< |
operator fun compareTo(o): Int (+ : Comparable<Self>) |
π’ | maps to a real Kotlin compareTo synthesized from the single < (this<o β -1, o<this β 1, else 0), so a < b/a > b/sorted() all work. GT/LTEQ/GTEQ stay infix (Kotlin derives them from compareTo). OpRelationalTest |
| OP-unary-minus | operator-() |
operator fun unaryMinus(): Vec2 |
π’ | real prefix -v |
| OP-assign | Vec2& operator= |
infix fun assign(o): Vec2? |
π’ | in-place copy works AND the Vec2& self-reference surfaces as a non-owning Vec2? (same OB-return-ref path), so the result is usable and chains. OpAssignTest |
| OP-pluseq | Vec2& operator+= |
infix fun plusEquals(o): Vec2? |
π’ | mutation works AND returns the receiver wrapper (non-owning Vec2?), so (a plusEquals b)!! plusEquals c chains onto the same storage. OpAssignTest |
| OP-call | operator() |
operator fun invoke(s: Double): Double |
π’ | idiomatic β v(s). A CALL operator (matches operator() of any arity) β KotlinOperator("invoke"). (Was a SIGABRT, briefly a _call fallback, now real invoke.) |
| OP-convert | explicit operator double() |
β | π΄ | conversion operators silently dropped (no toDouble); not in ALL_OPERATORS |
A Point2 aggregate + a PassObj struct of static methods exercising every
param/return passing convention (ObArgTest, ObReturnTest). All 9 rows π’ β
better than predicted. Key mechanic: pointer/reference returns get a uniform
non-owning wrapper (Type(rawptr, memScope), no Holder, no dispose), so they
neither double-free nor auto-free; only by-value returns get a _Holder
(placement-new). T* maps to a nullable Kotlin type end-to-end.
| ID | Convention | Generated Kotlin | Status | Notes |
|---|---|---|---|---|
| OB-byval-arg | by-value arg | describePoint(p: Point2): Unit |
π’ | real copy-on-entry; C++ mutating its copy doesn't touch the caller's |
| OB-constref-arg | const T& arg |
sumCoords(p: Point2): Int |
π’ | |
| OB-ptr-arg | T* arg (mutating) |
shiftPoint(p: Point2?, β¦) |
π’ | nullable; in-place mutation visible + accumulates |
| OB-mutate-ref | T& out-param |
zeroPoint(p: Point2): Unit |
π’ | no defensive copy β write-back lands on the caller's struct |
| OB-return-byval | return by value | makePoint(x,y): Point2 |
π’ | placement-new into Point2_Holder() |
| OB-return-ptr-borrowed | borrowed T* return |
borrowGlobal(): Point2? |
π’ | non-owning wrapper, no dispose β safe |
| OB-return-ptr-owned | owned T* return |
allocPoint()?.owned() or .dispose() |
π’ | returns a non-owning wrapper (default = borrowed, safer); caller opts into destruction via the generated .owned() (defers ~T() to scope end, returns this) or .dispose() (now). Surfaces the existing dispose C fn; virtual-aware. ObOwnedTest |
| OB-return-ref | T& return |
globalRef(): Point2? |
π’ | non-owning; write-through the binding's setters mutates the underlying object (predicted π΄, actually works) |
| OB-null-return | nullable T* return |
maybePoint(give): Point2? |
π’ | falseβnull, trueβnon-null |
A DefaultArgs/Buffer probe (DaDefaultOverloadTest). Overloads work; C++
default values are ABI-erased; variadic/initializer_list don't.
| ID | C++ | Generated Kotlin | Status | Notes |
|---|---|---|---|---|
| DA-default-arg | add(int, int=10, int=0) |
add(a, b, c): Int |
π’ | all params pass through |
| DA-omit-default | calling add(1) |
add(a, b: Int = 10, c: Int = 0) |
π’ | C++ default args surface as Kotlin default params β add(1)==11. Default value recovered from libclang (tokenize the default sub-expr); rendered per the param's Kotlin type |
| DA-default-method | format(int, int=0, char=' ') |
format(s, width: Int = 0, fill: Byte = ' '.code.toByte()) |
π’ | renderable literal defaults (int/float/char/bool/nullptrβnull) emit a Kotlin default; complex defaults (ctor calls, named consts, octal, bare-dot floats, non-Kotlin char escapes) fall back to a mandatory param. DaDefaultOverloadTest |
| DA-overload-free/member | process(int) / (int,int) / (double) |
process / process__int_int / process__double |
π’ | content-based, order-independent Kotlin names (overloadMethodName fix) |
| DA-const-overload | at(int) / at(int) const |
at / _at β CValuesRef<ByteVar>? |
π‘ | both wrapped (no collision/drop β constness isn't in the suffix, so the 2nd just gets a prefix); raw byte-ref return, no const distinction in the Kotlin type |
| DA-variadic | sumN(int, ...) |
sumN(count): Int |
π΄ | the ... is silently dropped (no variadic forwarding) β builds clean but inert |
| DA-initlist | sumIlist(std::initializer_list<int>) |
sumIlist(vals: Initializer_list__Int) |
π΄ | wrapped (not dropped like string_view) but inert β the initializer_list binding has only an empty ctor + size(), no way to populate it, so any list is empty |
A multi-namespace probe (NsSingleTest/NsNestedTest/NsNestedClassTest/
NsCollisionTest). C++ namespaces map to Kotlin packages cleanly.
| ID | C++ | Generated mapping | Status | Notes |
|---|---|---|---|---|
| NS-single | geo::Vec |
package geo, import geo.Vec, C symbols geo_Vec_* |
π’ | ctors/fields/const method/static all work |
| NS-nested | geo::detail::Impl |
package geo.detail, import geo.detail.Impl |
π’ | two-level namespace |
| NS-nested-class | Outer::Inner |
package outer, class Inner, import outer.Inner; Outer stays root.Outer |
π’ | the outer class name becomes a package segment β so two Outer::Inner separate naturally (the review's "collide in root" prediction was wrong) |
| NS-collision | acolor::Tag / bflavor::Tag |
distinct packages + distinct C prefixes | π’ | no link/runtime clash; same-file Kotlin use needs an import alias (import bflavor.Tag as FlavorTag) |
| NS-anon | anonymous namespace { Hidden } |
(skipped β not bound) | π’ | members are now skipped (TU-internal linkage, not referenceable across a C ABI) instead of emitting an invalid empty package line β WrappedElement.map returns null for an empty-spelling namespace |
| TI-inaccessible | public method over a protected/private nested type β int use(HiddenMode) / HiddenTag make() |
(method skipped β not bound) | π’ | a nested tag type declared protected/private can't be named from the free-function wrapper (the LLVM-22 repro: ASTContext::getPredefinedSugarType(clang::Type::PredefinedSugarKind), param is a protected enum). TypeBuilder resolves such a leaf to UNRESOLVABLE, so the method drops through the same notifyFailed path as any unbindable type; sibling methods over accessible types still bind. Replaces a manual per-symbol removeMethod fixup. TiInaccessibleTypeTest (#123) |
Single (one-base) public inheritance is flattened: a derived binding re-emits
its inherited public methods + fields so they're callable directly, with the
agreed naming rule (decision A). Probe Base/Derived (IhFlattenTest). The hard
polymorphism rows (base-pointer dispatch, upcast, multiple inheritance, abstract
suppression) are not built. Design decisions (naming + destruction/ownership) are in
the inheritance chunk + [[inheritance-decisions]] memory.
| ID | Feature | Generated Kotlin | Status | Notes |
|---|---|---|---|---|
| IH-base-method | inherited non-virtual method/field on a derived | derived.move(d), derived.bx, derived.baseOnly() |
π’ | flattened β calls the base's C wrapper on the derived ptr (single inheritance β offset 0, no this-adjust). Added an isVirtual flag to the model |
| IH-derived-method | derived-only method | derived.derivedOnly() |
π’ | |
| IH-virtual-override | overridden virtual via the derived binding | derived.area() β Derived_area |
π’ | the base's area copy is suppressed (one vtable slot); the override keeps the bare name and dispatches |
| IH-name-conflict | non-virtual method shadowing an inherited one | derived.derivedLabel() (own) / derived.label() (inherited) |
π’ | the shadowing descendant yields the name β <class><Method>; the base-most decl keeps label. Intrinsic to the hierarchy (import-set-independent), virtual overrides exempt |
| IH-virtual-dispatch | runtime polymorphism through a base pointer | areaOf(c: ShapeApi) β Circle's area() |
π’ | for a polymorphic base, generate interface <Base>Api (virtual surface); base + derived implement it (methods β override). Calling through the interface routes to the concrete wrapper's C call β the virtual C++ method vtable-dispatches. IhUpcastTest |
| IH-upcast | derivedβbase pointer | base-typed param/return β <Base>Api? |
π’ | a base-class-typed param/return is remapped to the base interface, so a Circle (IS-A ShapeApi) passes where Shape* is wanted (upcast = identity at offset 0). This implicit path is the offset-0 common case; for non-zero offsets (2nd base, Case-2 single) use the explicit offset-correct .asBase() (IH-multi-inherit π’) |
| IH-abstract | suppress ctor factory for a pure-virtual class | Button : DrawableApi; no Drawable() factory |
π’ | abstract class (pure-virtual member) drops its ctors (WrappedClass.resolve) so no construction factory is emitted, but it still gets a borrowable wrapper + its <Name>Api interface β usable polymorphically via a concrete subclass. A generated comment flags the absent factory. IhAbstractTest |
| IH-multi-inherit | multiple inheritance | widget2.asTagged().tag() |
π’ | uniform .asBase() upcast per (transitive, public, non-virtual) superclass, backed by a synthetic C helper D_as_B(p){ static_cast<B*>(reinterpret_cast<D*>(p)) } β applies the correct base-subobject offset. Works for the 2nd base (non-zero offset) AND the Case-2 single-inheritance non-zero offset (a non-poly base under a vtable-adding derived). IhCastTest. Diamond/virtual bases skipped (out of scope). Flatten + implicit upcast stay as offset-0 sugar; .asBase() is the correct general path |
| IH-destruction | scope-bound auto-release (virtual-aware) + owned/borrowed | defer { ~T() }; .owned()/.dispose(); nv-dtor WARNING |
π’ | (1) construction factories auto-release (defer { ~T() }, virtual-aware); (2) returned T* is borrowed by default β caller opts in via .owned()/.dispose() (ObOwnedTest); (3) a polymorphic class with no virtual dtor in its chain emits a generated WARNING (dtor virtuality threaded through the model, IhNvDtorTest) |
A method whose param/return type is a C function-pointer typedef
(typedef int (*IntTransform)(int)) used to be silently dropped β libclang gives it
as a typedef over a pointer-to-function-proto that fails canResolve, so the whole
method was filtered. Now captured as a WrappedFunctionPointer (an opaque NATIVE
pointer named by its typedef): the method survives, the Kotlin side surfaces the raw
cinterop CPointer<CFunction<(args)->ret>>?, the C side keeps the typedef name, and
the generated interop header re-declares the typedef (the name lives only in the user
header). Probe CbProbe (CbReturnTest). This is the CβKotlin half (the
already-real C pointer, callable + re-passable) β the "separate, easy" direction. The
KotlinβC direction is built too: a context-free callback takes a staticCFunction
directly (Mode 2), and a void*-context callback takes a plain capturing lambda via a
generated StableRef + trampoline (Mode 1, synchronous). Only the stored/async
callback (registration handle) remains, parked on a design decision. Full design in the
callbacks chunk + [[callback-decisions]].
| ID | Feature | Generated Kotlin | Status | Notes |
|---|---|---|---|---|
| CB-cfnptr-ret | method returns a C function-pointer typedef | val fn = ms.makeDouble(); fn(21) |
π’ | returned pointer is directly invokable through cinterop, no CFunction leak |
| CB-cfnptr-arg | method takes a C function-pointer typedef | ms.applyTransform(5, fn) |
π’ | a real C pointer (e.g. one returned from another binding) re-passes unchanged |
| CB-cfnptr-richsig | fn-pointer proto with enum/ref/class types in its signature | β | π΄ | stage-1 guard: only native-scalar/void protos re-declare verbatim in the C header; richer ones (e.g. libstdc++ event_callback) fall through to the drop, to avoid a conflicting typedef redefinition |
| CB-lambda-mode1 | capturing Kotlin lambda β C callback w/ void* context (synchronous) |
ms.callWith({ n -> n + base }, 5) |
π’ | the binding boxes the lambda in a StableRef, forwards a non-capturing staticCFunction trampoline (recovers it from the void* ctx) + the StableRef ptr, captures the result, disposes, returns. Both the fn-pointer + void* args consumed. Strict detection (one fn-pointer w/ leading void* proto slot + one adjacent plain void*), gated to plain-value returns. Pure KotlinWriter codegen. CbMode1Test |
| CB-lambda-mode1-async | stored/async callback (outlives the call) | β | π΄ | synchronous Mode-1 disposes the StableRef right after the call; a callback retained past the call needs a returned registration handle owning the StableRef (disposed on unregister()/scope exit). Parked on a design decision: emit the handle always, or only when the API provably stores the pointer |
| CB-lambda-mode2 | static lambda β context-free C callback | ms.applyTransform(7, staticCFunction { n -> n+1 }) |
π’ | falls out of stage 1: the param is the raw CPointer<CFunction<β¦>>?, so a caller passes staticCFunction { β¦ }; the compiler rejects a capturing lambda (the type enforces the Mode-2 limitation honestly). CbStaticLambdaTest |
| CB-std-function | std::function<β¦> param |
β | π΄ | boxes state; reuses the Mode-1 trampoline once built |
| CB-memfnptr | pointer-to-member-function | β | π΄ | no C ABI representation |
A user C++ template<class T> class Box {...} is instantiable from Kotlin as the
natural Box<Int>() β the syntax settled with the user. The generator emits a
marker interface Box<T> (no constructor) + a same-named scoped factory
@krapper.CppTemplate(...) fun <T> MemScope.Box(): Box<T>, so Box<Int>() resolves to
the function (a named call, refinable), which the FIR plugin refines to the concrete
Box__Int : Box<Int> and the IR extension lowers to its companion factory β the stdlib
List(n){} pattern, NOT constructor refinement (construction stays a scoped MemScope
factory; the interface is the typed surface). Rode in on the now-package-aware plugin
(see [[namespace-root-config]]). Design + the enabler facts in [[templates-decisions]].
| ID | Feature | Generated Kotlin | Status | Notes |
|---|---|---|---|---|
| TPL-class | instantiate a user class template | val b = Box<Int>(); b.set(7); b.get() |
π’ | interface Box<T> + MemScope.Box() facade β refined to Box__Int : Box<Int>. UtBoxTest. Bootstrap: the facade is itself generated, so the build seeds instantiate("Box<int>") (the compiler can't request it via SYNC_REQUIRED β chicken-and-egg) |
| TPL-methods | call instance methods on the instantiation | b.set(7), b.get() |
π’ | the refined call type is the concrete Box__Int, which carries the real (inline) methods, so they resolve even though the marker interface is empty |
| TPL-multi-arg | multi-type-param user template (Pair2<A,B>) |
Pair2<Int, Double>() |
π’ | worked out of the box β the facade machinery is arity-general (T1, T2; plugin derives arity from the facade's type params). Pair2__Int__Double : Pair2<Int, Double>. UtPair2Test |
| TPL-interface-methods | the generic Box<T> interface declaring methods |
fun readBox(b: Box<Int>): Int = b.get() |
π’ | interface Box<T> { fun set(x: T); fun get(): T }, concretes override. Inferred from the concrete instantiations (the raw template's T-typed methods don't survive resolution): a position is T only if it tracks the arg across every instantiation, else an identical concrete type, else the method is omitted. Needs β₯2 diverse instantiations to disambiguate. UtBoxAbstractTest |
| TPL-class-arg | template over a user/binding type (Box<Point>, Box<Box<Int>>) |
Box<Point>(), Box<Box__Int>() |
π’ | generator's cppArgToKotlinType derives the arg's Kotlin type from the generated binding (Point / mangled Box__Int); plugin already resolved @CppBinding element types (the cppVector<Vector__Int> path). Nested instantiation uses the concrete mangled name (Box__Int), like containers. UtBoxPointTest / UtBoxNestedTest |
| TPL-nontype-param | non-type template params (Fixed<N>) |
β | π΄ | no Kotlin reified equivalent (same blocker as CP-tuple) |
| TPL-free-fn | free function templates (maxOf<Int>) |
β | π΄ | CXCursor_FunctionTemplate β krapper doesn't wrap free functions |
| Feature | Why |
|---|---|
long double |
x86-64 long double is 80-bit extended precision; Kotlin/Native has no 80-bit float, so cinterop can only refuse or silently truncate to Double (losing precision). Excluded rather than scaffolded β the generator should emit a clear "use double" diagnostic rather than a lossy binding. |
-
(FIXED β now maps tooperator()crashes the generatorinvoke). A class withoperator()used to abort the whole sync (exit 134): it wasn't inALL_OPERATORS, fell through to the fallback namer whose parens weren't sanitized β invalid identifier β compile failure β SIGABRT. Now aBasicCallOperator(an any-arity matcher) +ResolvedOperator.CALLβKotlinOperator("invoke")generates a realoperator fun invoke, sov(s)works (OP-call π’). The()β_callsanitization incleanupName/kotlinMethodNameremains as a defensive fallback. -
Anonymous namespace emits invalid
package. A struct in an anonymousnamespace { }generates a binding file with a literal emptypackagedeclaration (the empty namespace name), which fails Kotlin compilation (Package name must be a '.'-separated identifier list). It does NOT crash the generator (theerror("Namespace without name")guard a review predicted is not hit). Fix: skip anonymous-namespace members in the parser (they're TU-internal anyway), or emit them into the root package. (Surfaced scaffolding namespaces.) -
Namespace-scope free functions are not wrapped. krapper_gen only wraps struct/class members; a global/namespace free function is silently dropped from the generated binding entirely (not in the
.kt/.h/.cc). Probes expose their surface asstaticmethods of a struct (asStringFeature/PassObjdo) to work around it. If free-function support is expected, it's a gap. (Note:std::free functions DO appear via*_Functions.kt, so this may be specific to root/user-namespace functions β worth confirming.) -
Overloaded regular method Kotlin names were order-based(FIXED for distinct-arg overloads). The overload-naming fix now also covers non-constructor methods:_-prefixKotlinWriter.overloadMethodName(mirroringconstructorFactoryName) gives the first overload the bare name and each additional same-name method a__<argtypes>suffix (e.g.append/append__const_char_P_size_t/append__size_t_char), so distinct-arg overloads are order-independent. Zero test churn (existing tests use first-overload bare names). Remaining: const/non-const overloads have identical arg types, so their suffixes collide and they still fall through to the_-prefix uniquifier (at/_at) β encoding constness in the suffix is a smaller follow-up. -
(FIXED β nowoperator==βinfix fun eqequals+hashCode).operator==now generates a real Kotlinequalsoverride + an always-presenthashCode(folds public fields,return 0if none) βa == bworks idiomatically and the equals/hashCode contract holds. -
Forcing-TU missed nested element headers(FIXED). The v2 instantiation flow synthesizes a throwaway "forcing" header (struct KrapperForce { std::vector<std::string> value; };) to make libclang instantiate a requested template. It only#included the outer base's std header (<vector>), sostd::vector<std::string>failed to parse (basic_stringundeclared) andstd::vector<Point>would miss the user type. Fixed inIndexedServiceImpl.requestInstantiationto include a bundle of common std headers plus the consumer's own headers. Unblocked CV-elem-string and CV-elem-class. (v2-only code β not on main, so fixed directly here.) -
(FIXED). Standard containers/strings definesize_type/difference_typemember typedefs dropped methodssize_type(=std::size_t) anddifference_type(=std::ptrdiff_t) via dependent trait expressions (_Rep_type::size_type,__alloc_traits<β¦>::size_type) that libclang leaves unexposed β so methods returning/taking them (size,count,max_size,erase(key),length,resize, β¦) were silently dropped, exactly like thereferenceaccessor case. Fixed inWrappedTypewith asizeTypedefElementreducer mapping those typedefs to the concrete integral types. Recovered mapsize/count/erase(key)(CM-size/count/erase β π’) and std::string'ssize/length/resize. Side effect: recovering std::string's size_type-taking constructors reshuffled the_-prefix overload factory names (the const-char* ctor moved__Basic_string__Charβ_____Basic_string__Char) β see the brittle-overload-naming note below. This fix lives on v2-templates; should be backported tomainalongside the reference-typedef fix (e38ff3f), since both are in the shared resolver. -
Brittle(FIXED). Generated constructor/overload names were disambiguated by a leading-underscore count tied to overload order (_-prefix overload factory naming_,__,___, β¦), so adding/removing any resolvable overload shifted every later name and silently broke call sites (seen with the c++17 bump and the size_type fix). Now both naming layers are content-based: the first method to claim a base name keeps it, and later overloads get a__<argtypes>suffix derived only from their own signature β order-independent. Layer 1 = C function names (NameHandler.uniqueOverloadName, e.g.TestClass_new__int_double); layer 2 = the KotlinMemScope.<Class>(...)factory names (KotlinWriter.constructorFactoryName, e.g.Point__int_int,Basic_string__Char__const_char_P). Tradeoff: the names are longer/uglier but stable; a cleaner future option is to emit same-named Kotlin overloads where they aren't genuinely ambiguous. Updated the golden tests- featuregen call sites that pinned the old order-based names.
-
krapper_gen discards C typedef aliases(FIXED forsize_t/ssize_t/ptrdiff_t/intptr_t/wchar_t). Generated bindings collapsed these to their bare underlying Kotlin type (e.g.std::vector::size()returnedULong, aptrdiff_tparam returnedLong), whereas cinterop preserves the platform-correctplatform.posix.<name>alias β so the same C++size_tsurfaced as two different Kotlin types depending on binding path. Now these aliases are preserved end-to-end (with theimport platform.posix.<name>), consistent across vector + map + direct params. Verified byPrAliasTest(echoDiff/echoWide) plus the containersize/countetc. Root causes fixed:- The
size_type/difference_typemember-typedef reducer emitted the bare integer; now emitssize_t/ptrdiff_t, un-gated so it also covers containers whose typedef resolved on its own (vector). fullyQualifiedTypeunconditionally capitalized the last segment, mangling the lowercase typealiasplatform.posix.size_tinto the undefinedSize_t; now capitalizes only wrapper class names.- A direct use of one of these typedefs was followed by libclang's
visit()to its underlying integer;cAliasTypedefElementnow captures the alias name before that happens. - The aliases were added to the native list +
typeMap+pointerTypeMap, and the generated header now includes<stddef.h>soptrdiff_t/wchar_tare in scope in the C wrapper. (ThePR-size-t/PR-wcharcinterop rows were π’ regardless.)char16_t/char32_t/char8_taliases not yet covered.
- The
-
Return-by-value class types crash(FIXED). The generated C++ wrapper used to emit*ret_value_cast = Foo::bar();β copy/move assignment into the raw, unconstructed memory from*_Holder()(size/align alloc, no constructor), which is UB and crashed at runtime for non-trivial types. Fixed in krapper_genCppWriter(ARG_CAST return style) to placement-new instead:new (ret_value_cast) T(Foo::bar());. This fix covers every by-value class return (std::string, and std::vector etc. when those rows land), not just the string rows. Validated by ST-string-rt / ST-string-ret going π’. -
krapper_gen parses at(config added; deeper blockers remain). The standard is now configurable end-to-end:--std=c++14kplusplus { cppStandard = "c++17" }β krapper_gen--stdflag β both the libclang parse (IndexedServiceImpl) and the wrapper compile (CppCompiler). That alone does NOT unblockST-stringview-in, which surfaced two further krapper_gen gaps:std::string_viewisn't wrapped as a bindable type β a method taking it is silently dropped from the facade (same failure mode asstd::basic_string<wchar_t>below).- Under c++17 the generated
std::stringbinding loses itsconst char*constructor (the factory set changes when the string_view-convertible template ctor enters overload resolution), so the existing string rows can't even construct a std::string. The_-suffix overload disambiguation is standard-sensitive and brittle. Net: the standard knob is a necessary prerequisite that now exists, butstring_viewneeds the type-wrapping fix and a more robust constructor-overload mapping before it can go π’.
-
std::basic_string<wchar_t>is not wrapped (ST-wstring). Onlybasic_string<char>gets a binding; methods that take/returnstd::wstringare silently filtered out of the generated facade. The silent drop is itself a problem β a wrapped function vanishing should at least warn. -
Container element accessors are dropped(FIXED).std::vector'soperator[],at(),front(),back()now generate (returning a pointer to the element). Root cause was libstdc++ defining vector'sreference/const_referenceas a dependent trait typedef (__alloc_traits<β¦>::reference) that reduced to an opaque template ref the resolver couldn't map, so the methods were silently dropped (the failure log was gated to the v8 "CreateParams" debugFilter). Fixed in krapper_genWrappedTypeby rebuilding those member typedefs asvalue_type&/const value_type&, routed through the existing&(...)pointer-return path; gated by aWrappedTemplateTypecheck so associative containers (std::map, whosevalue_typeispair<const K,V>) don't regress. Logging is now env-gated (KRAPPER_DEBUG_RESOLVE) instead of hardcoded. Fixed onmain(e38ff3f), cherry-picked here. Validated: CV-index-get/front-back/iterate π’, slice + map unchanged, krapper_gen unit tests show only the 2 pre-existing failures. Re-checked against string_view/wstring: does NOT recover them β distinct root causes.wstring(ST-wstring) still drops becausestd::basic_string<wchar_t>isn't wrapped at all (no binding generated), andstring_view(ST-stringview-in) needs c++17 and the view type isn't wrapped. The reference-typedef fix was specific to sequence-container element accessors returning thereferencemember typedef.Original diagnosis (for the record)
- The accessors *are* parsed β cursor dump shows them public + `Available`, both const and non-const overloads, with distinct USRs (so it is **not** a name/USR cache collision). - They are dropped during **resolution** β `WrappedMethod.resolve` returns null. The failure is **silent**: `ResolveContext.notifyFailed` only logs when a `debugFilter` matches (currently hardcoded to "CreateParams"), so these failures never surface. - The discriminator is the **return type**: the survivors return a pointer (`data()` β `_Tp*`) or a directly-spelled element ref that resolves (`std::map::operator[]` β `mapped_type&` β `int&`, which *does* survive). The casualties return vector's **`reference` / `const_reference` member typedef**, which resolution can't map β null. So the bug is in resolving the `reference` member-typedef return (probably an unresolved dependent typedef), not in references per se. - Fix direction: (1) un-gate `notifyFailed` logging so dropped methods are visible; (2) resolve the `reference`/`const_reference` member typedefs to the underlying element ref. Likely also recovers string_view/wstring drops. Highest-value container fix; needs deliberate work in the resolver.