From fd7e67ce07f17258ee6fdaae77938b66b8b520bc Mon Sep 17 00:00:00 2001 From: Ayuse Date: Thu, 15 Oct 2026 10:25:38 +0100 Subject: [PATCH 1/8] Add C++ vector constructor overload experiment --- cpp-overload-test/src/lib.rs | 1 + cpp-overload-test/src/stdcpp/vector.rs | 211 +++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 cpp-overload-test/src/stdcpp/vector.rs diff --git a/cpp-overload-test/src/lib.rs b/cpp-overload-test/src/lib.rs index b72552c..b0837b3 100644 --- a/cpp-overload-test/src/lib.rs +++ b/cpp-overload-test/src/lib.rs @@ -8,4 +8,5 @@ pub mod stdcpp { // Add new modules for each type here, in alphabetical order. pub mod forward_list; + pub mod vector; } diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs new file mode 100644 index 0000000..0aab68b --- /dev/null +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -0,0 +1,211 @@ +//! Check whether C++ `std::vector` constructors can be represented as Rust overloads. +//! +//! ### Limitations +//! +//! This experiment fixes the element type to C++ `int`; it does not test generic overloads. +//! The slice overload represents the C++ iterator-pair constructor using contiguous input, +//! rather than exposing arbitrary C++ iterators. Allocator, copy, move, initializer-list, +//! and C++23 range constructors are not covered here. C++ allocation exceptions are not +//! translated into Rust errors. +//! +//! ### Ergonomics +//! +//! Counts use `c_size_t`, while elements use `c_int`. Callers can use explicit argument +//! types to select the intended overload. Arrays must be converted to slices explicitly +//! for the slice overload. The count-and-value overload preserves C++'s argument order. +//! A possible improvement is to accept array references directly, avoiding the explicit +//! conversion to a slice. This would need investigation into the macro's support for +//! array lengths and overload selection. + +#![allow(unused_braces, reason = "Generated by overload! macro")] + +use cpp::cpp; +use splat_overload::overload; +use std::ffi::{c_int, c_size_t, c_void}; + +cpp! {{ + #include + #include +}} + +/// Owns a C++ `std::vector`; its pointer stays private so callers cannot replace it. +pub struct StdVector(*mut c_void); + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +impl StdVector { + fn empty() -> StdVector { + let vector = unsafe { + cpp!([] -> *mut c_void as "std::vector*" { + return new std::vector(); + }) + }; + StdVector(vector) + } + + fn repeat_default(count: c_size_t) -> StdVector { + let vector = unsafe { + cpp!([count as "size_t"] -> *mut c_void as "std::vector*" { + return new std::vector(count); + }) + }; + StdVector(vector) + } + + fn repeat_with(count: c_size_t, value: c_int) -> StdVector { + let vector = unsafe { + cpp!([count as "size_t", value as "int"] -> *mut c_void as "std::vector*" { + return new std::vector(count, value); + }) + }; + StdVector(vector) + } + + fn from_slice(items: &[c_int]) -> StdVector { + let count = items.len(); + let items = items.as_ptr(); + let vector = unsafe { + cpp!([items as "const int*", count as "size_t"] -> *mut c_void as "std::vector*" { + // An empty Rust slice may have a dangling pointer. Use a real C++ object + // for its empty iterator range, avoiding arithmetic on that pointer. + const int empty = 0; + const int* first = count == 0 ? &empty : items; + return new std::vector(first, first + count); + }) + }; + StdVector(vector) + } +} + +// cpp_build must see cpp! calls directly in the source, outside overload! expansion. +overload! { + impl StdVector { + /// Construct an empty vector. + pub fn new() -> StdVector { + StdVector::empty() + } + + /// Construct `count` default-initialized integer elements (zeros). + pub fn new(count: c_size_t) -> StdVector { + StdVector::repeat_default(count) + } + + /// Construct `count` copies of `value`, following C++ argument order. + pub fn new(count: c_size_t, value: c_int) -> StdVector { + StdVector::repeat_with(count, value) + } + + /// Copy a slice using the C++ iterator-pair constructor. + pub fn new(items: &[c_int]) -> StdVector { + StdVector::from_slice(items) + } + } +} + +impl Drop for StdVector { + fn drop(&mut self) { + let vector = self.0; + // Each constructor allocates a distinct vector. The wrapper cannot be copied, + // and no accessor exposes its pointer, so this deletes it exactly once. + unsafe { + cpp!([vector as "std::vector*"] { + delete vector; + }) + } + } +} + +#[cfg(test)] +impl StdVector { + fn len(&self) -> c_size_t { + let vector = self.0; + unsafe { + cpp!([vector as "const std::vector*"] -> c_size_t as "size_t" { + return vector->size(); + }) + } + } + + fn get(&self, index: c_size_t) -> Option { + if index >= self.len() { + return None; + } + let vector = self.0; + // The bounds check precedes the read; returning a copy avoids exposing a + // reference into C++ storage with a Rust lifetime. + Some(unsafe { + cpp!([vector as "const std::vector*", index as "size_t"] -> c_int as "int" { + return (*vector)[index]; + }) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_contents(vector: &StdVector, expected: &[c_int]) { + assert_eq!(vector.len(), expected.len()); + for (index, value) in expected.iter().enumerate() { + assert_eq!(vector.get(index), Some(*value)); + } + assert_eq!(vector.get(expected.len()), None); + } + + #[test] + fn empty_constructor() { + let vector: StdVector = StdVector::new(); + assert_contents(&vector, &[]); + } + + #[test] + fn count_constructor_initializes_every_element() { + let vector: StdVector = StdVector::new(4_usize); + assert_contents(&vector, &[0, 0, 0, 0]); + } + + #[test] + fn count_constructor_accepts_zero() { + let vector: StdVector = StdVector::new(0_usize); + assert_contents(&vector, &[]); + } + + #[test] + fn fill_constructor_preserves_count_and_value_order() { + let vector: StdVector = StdVector::new(3_usize, 7 as c_int); + assert_contents(&vector, &[7, 7, 7]); + + let negative: StdVector = StdVector::new(2_usize, -5 as c_int); + assert_contents(&negative, &[-5, -5]); + } + + #[test] + fn fill_constructor_accepts_zero_count() { + let vector: StdVector = StdVector::new(0_usize, 7 as c_int); + assert_contents(&vector, &[]); + } + + #[test] + fn range_constructor_copies_the_selected_elements() { + let vector: StdVector = { + let mut source: Vec = vec![99, 2, 5, 9, 100]; + let vector: StdVector = StdVector::new(&source[1..4]); + source[2] = -1; + assert_contents(&vector, &[2, 5, 9]); + vector + }; + // The C++ vector owns its elements even after the source has been dropped. + assert_contents(&vector, &[2, 5, 9]); + } + + #[test] + fn range_constructor_accepts_empty_slices() { + let empty: &[c_int] = &[]; + let vector: StdVector = StdVector::new(empty); + assert_contents(&vector, &[]); + + let source: [c_int; 2] = [2, 5]; + let vector: StdVector = StdVector::new(&source[2..]); + assert_contents(&vector, &[]); + } +} From 70adf50b0da9f847cbe33feba763d61ff79d89e3 Mon Sep 17 00:00:00 2001 From: Ayuse Date: Thu, 15 Oct 2026 11:01:41 +0100 Subject: [PATCH 2/8] Document vector constructor coverage and deferred overloads --- cpp-overload-test/src/stdcpp/vector.rs | 31 ++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs index 0aab68b..678b344 100644 --- a/cpp-overload-test/src/stdcpp/vector.rs +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -4,10 +4,37 @@ //! //! This experiment fixes the element type to C++ `int`; it does not test generic overloads. //! The slice overload represents the C++ iterator-pair constructor using contiguous input, -//! rather than exposing arbitrary C++ iterators. Allocator, copy, move, initializer-list, -//! and C++23 range constructors are not covered here. C++ allocation exceptions are not +//! rather than exposing arbitrary C++ iterators. C++ allocation exceptions are not //! translated into Rust errors. //! +//! ### Constructor coverage +//! +//! Compared against the [C++ vector constructor declarations](https://eel.is/c++draft/vector.overview). +//! The default, count, count-and-value, and iterator-pair constructors are exercised +//! through Rust `new` overloads. The iterator-pair case is tested with pointers into a +//! slice, not with arbitrary input iterators. Explicit allocator arguments, including +//! allocator-only and allocator-extended copy/move constructors, are excluded by the +//! contribution task's allocator-overload rule. +//! +//! The remaining constructors are deferred, not demonstrated to be inexpressible: +//! - Copy construction (`vector(const vector&)`) needs an overload borrowing another +//! wrapper and tests showing an independent copy. It is outside this initial set of +//! four constructors; `new(&StdVector)` is a possible extension. +//! - Move construction (`vector(vector&&)`) needs an explicit ownership mapping and +//! tests for the moved-from wrapper. A Rust move of this wrapper only transfers its +//! pointer and does not call the C++ move constructor. The existing forward-list +//! example's mutable-reference approach is a possible extension to investigate. +//! - Initializer-list construction is not exercised by the slice overload: that helper +//! calls the iterator-pair constructor. Rust has no direct `std::initializer_list` +//! type; a distinct wrapper or fixed-arity overloads, as in the forward-list example, +//! would need separate tests. Mapping it to the same slice signature would not let +//! callers select between the two C++ constructors. +//! - C++23 `vector(std::from_range, range)` needs a range bridge and a distinct tag or +//! argument type to distinguish it from the existing slice overload. It is deferred +//! pending that design; the current slice test does not exercise this constructor. +//! +//! These scope choices require mentor review; this is not complete constructor coverage. +//! //! ### Ergonomics //! //! Counts use `c_size_t`, while elements use `c_int`. Callers can use explicit argument From 3cdb9542ec0eb3e8c7e65505cbe481f1c91acff0 Mon Sep 17 00:00:00 2001 From: Ayuse Date: Thu, 15 Oct 2026 12:22:49 +0100 Subject: [PATCH 3/8] Move vector documentation beside relevant constructors --- cpp-overload-test/src/stdcpp/vector.rs | 89 ++++++++++++++------------ 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs index 678b344..93b705e 100644 --- a/cpp-overload-test/src/stdcpp/vector.rs +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -3,46 +3,7 @@ //! ### Limitations //! //! This experiment fixes the element type to C++ `int`; it does not test generic overloads. -//! The slice overload represents the C++ iterator-pair constructor using contiguous input, -//! rather than exposing arbitrary C++ iterators. C++ allocation exceptions are not -//! translated into Rust errors. -//! -//! ### Constructor coverage -//! -//! Compared against the [C++ vector constructor declarations](https://eel.is/c++draft/vector.overview). -//! The default, count, count-and-value, and iterator-pair constructors are exercised -//! through Rust `new` overloads. The iterator-pair case is tested with pointers into a -//! slice, not with arbitrary input iterators. Explicit allocator arguments, including -//! allocator-only and allocator-extended copy/move constructors, are excluded by the -//! contribution task's allocator-overload rule. -//! -//! The remaining constructors are deferred, not demonstrated to be inexpressible: -//! - Copy construction (`vector(const vector&)`) needs an overload borrowing another -//! wrapper and tests showing an independent copy. It is outside this initial set of -//! four constructors; `new(&StdVector)` is a possible extension. -//! - Move construction (`vector(vector&&)`) needs an explicit ownership mapping and -//! tests for the moved-from wrapper. A Rust move of this wrapper only transfers its -//! pointer and does not call the C++ move constructor. The existing forward-list -//! example's mutable-reference approach is a possible extension to investigate. -//! - Initializer-list construction is not exercised by the slice overload: that helper -//! calls the iterator-pair constructor. Rust has no direct `std::initializer_list` -//! type; a distinct wrapper or fixed-arity overloads, as in the forward-list example, -//! would need separate tests. Mapping it to the same slice signature would not let -//! callers select between the two C++ constructors. -//! - C++23 `vector(std::from_range, range)` needs a range bridge and a distinct tag or -//! argument type to distinguish it from the existing slice overload. It is deferred -//! pending that design; the current slice test does not exercise this constructor. -//! -//! These scope choices require mentor review; this is not complete constructor coverage. -//! -//! ### Ergonomics -//! -//! Counts use `c_size_t`, while elements use `c_int`. Callers can use explicit argument -//! types to select the intended overload. Arrays must be converted to slices explicitly -//! for the slice overload. The count-and-value overload preserves C++'s argument order. -//! A possible improvement is to accept array references directly, avoiding the explicit -//! conversion to a slice. This would need investigation into the macro's support for -//! array lengths and overload selection. +//! C++ allocation exceptions are not translated into Rust errors. #![allow(unused_braces, reason = "Generated by overload! macro")] @@ -104,6 +65,33 @@ impl StdVector { } // cpp_build must see cpp! calls directly in the source, outside overload! expansion. +// ### Constructor coverage +// +// Compared against the [C++ vector constructor declarations](https://eel.is/c++draft/vector.overview). +// The default, count, count-and-value, and iterator-pair constructors are exercised +// through Rust `new` overloads. Explicit allocator arguments, including +// allocator-only and allocator-extended copy/move constructors, are excluded by the +// contribution task's allocator-overload rule. +// +// The remaining constructors are deferred, not demonstrated to be inexpressible: +// - Copy construction (`vector(const vector&)`) needs an overload borrowing another +// wrapper and tests showing an independent copy. It is outside this initial set of +// four constructors; `new(&StdVector)` is a possible extension. +// - Move construction (`vector(vector&&)`) needs an explicit ownership mapping and +// tests for the moved-from wrapper. A Rust move of this wrapper only transfers its +// pointer and does not call the C++ move constructor. The existing forward-list +// example's mutable-reference approach is a possible extension to investigate. +// - Initializer-list construction is not exercised by the slice overload: that helper +// calls the iterator-pair constructor. Rust has no direct `std::initializer_list` +// type; a distinct wrapper or fixed-arity overloads, as in the forward-list example, +// would need separate tests. Mapping it to the same slice signature would not let +// callers select between the two C++ constructors. +// - C++23 `vector(std::from_range, range)` needs a range bridge and a distinct tag or +// argument type to distinguish it from the existing slice overload. It is deferred +// pending that design; the current slice test does not exercise this constructor. +// +// These scope choices require mentor review; this is not complete constructor coverage. +// overload! { impl StdVector { /// Construct an empty vector. @@ -112,16 +100,37 @@ overload! { } /// Construct `count` default-initialized integer elements (zeros). + /// + /// ### Ergonomics + /// + /// Counts use `c_size_t`; callers can use an explicit argument type to select + /// this overload. pub fn new(count: c_size_t) -> StdVector { StdVector::repeat_default(count) } /// Construct `count` copies of `value`, following C++ argument order. + /// + /// ### Ergonomics + /// + /// Counts use `c_size_t`, while elements use `c_int`. Callers can use explicit + /// argument types to select this overload. pub fn new(count: c_size_t, value: c_int) -> StdVector { StdVector::repeat_with(count, value) } /// Copy a slice using the C++ iterator-pair constructor. + /// + /// ### Limitations + /// + /// Represents the iterator-pair constructor using contiguous input, rather + /// than exposing arbitrary C++ iterators. + /// + /// ### Ergonomics + /// + /// Arrays must be converted to slices explicitly. A possible improvement is + /// to accept array references directly, avoiding that conversion. This needs + /// investigation into the macro's support for array lengths and overload selection. pub fn new(items: &[c_int]) -> StdVector { StdVector::from_slice(items) } From ffe330ee1e1a14ee991e65b88a53f10d6a0327c6 Mon Sep 17 00:00:00 2001 From: Ayuse Date: Thu, 15 Oct 2026 13:35:03 +0100 Subject: [PATCH 4/8] Shorten vector constructor coverage comments --- cpp-overload-test/src/stdcpp/vector.rs | 37 +++++++------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs index 93b705e..84f9a95 100644 --- a/cpp-overload-test/src/stdcpp/vector.rs +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -64,34 +64,17 @@ impl StdVector { } } +// Constructor coverage: https://eel.is/c++draft/vector.overview +// Allocator variants are excluded by the task instructions. +// Deferred constructors (not proven inexpressible): +// - Copy: needs an overload borrowing another wrapper and independent-copy tests. +// - Move: needs ownership and moved-from-state tests; moving the Rust wrapper does +// not invoke the C++ move constructor. +// - Initializer-list: needs a distinct input representation; the slice overload +// calls the iterator-pair constructor instead. +// - C++23 from_range: needs a range bridge and a distinct tag or argument type. + // cpp_build must see cpp! calls directly in the source, outside overload! expansion. -// ### Constructor coverage -// -// Compared against the [C++ vector constructor declarations](https://eel.is/c++draft/vector.overview). -// The default, count, count-and-value, and iterator-pair constructors are exercised -// through Rust `new` overloads. Explicit allocator arguments, including -// allocator-only and allocator-extended copy/move constructors, are excluded by the -// contribution task's allocator-overload rule. -// -// The remaining constructors are deferred, not demonstrated to be inexpressible: -// - Copy construction (`vector(const vector&)`) needs an overload borrowing another -// wrapper and tests showing an independent copy. It is outside this initial set of -// four constructors; `new(&StdVector)` is a possible extension. -// - Move construction (`vector(vector&&)`) needs an explicit ownership mapping and -// tests for the moved-from wrapper. A Rust move of this wrapper only transfers its -// pointer and does not call the C++ move constructor. The existing forward-list -// example's mutable-reference approach is a possible extension to investigate. -// - Initializer-list construction is not exercised by the slice overload: that helper -// calls the iterator-pair constructor. Rust has no direct `std::initializer_list` -// type; a distinct wrapper or fixed-arity overloads, as in the forward-list example, -// would need separate tests. Mapping it to the same slice signature would not let -// callers select between the two C++ constructors. -// - C++23 `vector(std::from_range, range)` needs a range bridge and a distinct tag or -// argument type to distinguish it from the existing slice overload. It is deferred -// pending that design; the current slice test does not exercise this constructor. -// -// These scope choices require mentor review; this is not complete constructor coverage. -// overload! { impl StdVector { /// Construct an empty vector. From 218e13fc08b3c45e16f3d104cf082ae98b7c160e Mon Sep 17 00:00:00 2001 From: Ayuse Date: Fri, 23 Oct 2026 16:03:44 +0100 Subject: [PATCH 5/8] Expand vector overload coverage and ownership tests --- cpp-overload-test/src/stdcpp/vector.rs | 674 +++++++++++++++++++++++-- 1 file changed, 631 insertions(+), 43 deletions(-) diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs index 84f9a95..a3c283a 100644 --- a/cpp-overload-test/src/stdcpp/vector.rs +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -1,78 +1,254 @@ -//! Check whether C++ `std::vector` constructors can be represented as Rust overloads. +//! Check whether C++ `std::vector` overloads can be represented in Rust. //! //! ### Limitations //! //! This experiment fixes the element type to C++ `int`; it does not test generic overloads. -//! C++ allocation exceptions are not translated into Rust errors. +//! +//! Rust zero-sized element types (ZSTs), such as `()`, are not supported. A nonempty +//! Rust slice of ZSTs has a logical element count but needs no element storage, and +//! pointer offsets do not advance its address. References must still be non-null +//! and suitably aligned. In C++, even an empty class has a nonzero `sizeof`, and its +//! array elements occupy storage. Empty base classes and `[[no_unique_address]]` +//! members can occupy no additional storage, but these exceptions do not make +//! ordinary vector elements zero-sized. +//! +//! The slice constructor passes the Rust element pointer to C++ as `const item_t*` +//! and forms the iterator range from `first` to `first + count`. This requires +//! compatible Rust and C++ element representations and valid backing storage for +//! that range. A Rust ZST slice cannot be reinterpreted as an array of nonzero-sized +//! C++ objects. The local C++ fallback object handles only `count == 0`; a nonempty +//! ZST slice would bypass it. `PhantomData` records ownership but supplies no +//! element storage. Supporting ZSTs would require a separate conversion strategy, +//! rather than simply changing the `T` and `item_t` aliases. +//! +//! See [Rust ZSTs], [Rust Vec guarantees], [C++ object sizes], and [C++ sizeof]. +//! +//! Each constructor uses throwing C++ `new` to allocate the vector object, even for an +//! empty vector. Allocation failure is reported by throwing `std::bad_alloc`, rather +//! than returning a null pointer. Allocating the vector's element storage can also +//! throw `std::bad_alloc`. Excessive element counts can cause other exceptions; for +//! example, libc++ throws `std::length_error` when a count exceeds `max_size()`. +//! +//! Neither these constructors nor the wrappers generated by rust-cpp 0.5.11 catch +//! C++ exceptions. The generated Rust declarations use `extern "C"`, which does not +//! permit unwinding. If a C++ exception unwinds across this boundary into Rust, the +//! behavior is undefined; a Rust panic or clean process abort is not guaranteed. +//! These constructors therefore do not safely handle allocation failures. +//! +//! Safe handling would require catching exceptions on the C++ side before they cross +//! the boundary, then returning an explicit error indication or deliberately +//! terminating the process. Using `new (std::nothrow)` alone would not handle exceptions +//! thrown by the vector constructor while allocating element storage. +//! +//! References: [C++ allocation functions], [the default allocator], [libc++ vector], +//! and [Rust FFI unwinding]. +//! +//! [C++ allocation functions]: https://eel.is/c++draft/new.delete.single +//! [the default allocator]: https://eel.is/c++draft/allocator.members +//! [libc++ vector]: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__vector/vector.h +//! [Rust FFI unwinding]: https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding +//! [Rust ZSTs]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts +//! [Rust Vec guarantees]: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees +//! [C++ object sizes]: https://eel.is/c++draft/intro.object +//! [C++ sizeof]: https://eel.is/c++draft/expr.sizeof #![allow(unused_braces, reason = "Generated by overload! macro")] use cpp::cpp; use splat_overload::overload; -use std::ffi::{c_int, c_size_t, c_void}; +use std::ffi::{c_size_t, c_void}; +use std::marker::PhantomData; + +pub type T = std::ffi::c_int; cpp! {{ #include #include + #include + #include + #include + + typedef int item_t; }} /// Owns a C++ `std::vector`; its pointer stays private so callers cannot replace it. -pub struct StdVector(*mut c_void); +pub struct StdVector(*mut c_void, PhantomData); #[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] impl StdVector { fn empty() -> StdVector { let vector = unsafe { - cpp!([] -> *mut c_void as "std::vector*" { - return new std::vector(); + cpp!([] -> *mut c_void as "std::vector*" { + return new std::vector(); }) }; - StdVector(vector) + StdVector(vector, PhantomData) } fn repeat_default(count: c_size_t) -> StdVector { let vector = unsafe { - cpp!([count as "size_t"] -> *mut c_void as "std::vector*" { - return new std::vector(count); + cpp!([count as "size_t"] -> *mut c_void as "std::vector*" { + return new std::vector(count); + }) + }; + StdVector(vector, PhantomData) + } + + fn repeat_with(count: c_size_t, value: T) -> StdVector { + let vector = unsafe { + cpp!([count as "size_t", value as "item_t"] -> *mut c_void as "std::vector*" { + return new std::vector(count, value); + }) + }; + StdVector(vector, PhantomData) + } + + fn move_from(other: &mut StdVector) -> StdVector { + let other = other.0; + let vector = unsafe { + cpp!([other as "std::vector*"] -> *mut c_void as "std::vector*" { + return new std::vector(std::move(*other)); + }) + }; + StdVector(vector, PhantomData) + } + + fn copy_from(other: &StdVector) -> StdVector { + let other = other.0 as *const c_void; + let vector = unsafe { + cpp!([other as "const std::vector*"] -> *mut c_void as "std::vector*" { + return new std::vector(*other); + }) + }; + StdVector(vector, PhantomData) + } + + fn from_initializer_list_1(a: T) -> StdVector { + let vector = unsafe { + cpp!([a as "item_t"] -> *mut c_void as "std::vector*" { + return new std::vector{ a }; + }) + }; + StdVector(vector, PhantomData) + } + + fn from_initializer_list_2(a: T, b: T) -> StdVector { + let vector = unsafe { + cpp!([a as "item_t", b as "item_t"] -> *mut c_void as "std::vector*" { + return new std::vector{ a, b }; }) }; - StdVector(vector) + StdVector(vector, PhantomData) } - fn repeat_with(count: c_size_t, value: c_int) -> StdVector { + fn from_initializer_list_3(a: T, b: T, c: T) -> StdVector { let vector = unsafe { - cpp!([count as "size_t", value as "int"] -> *mut c_void as "std::vector*" { - return new std::vector(count, value); + cpp!([a as "item_t", b as "item_t", c as "item_t"] -> *mut c_void as "std::vector*" { + return new std::vector{ a, b, c }; }) }; - StdVector(vector) + StdVector(vector, PhantomData) } - fn from_slice(items: &[c_int]) -> StdVector { + fn from_iter(iter: &mut dyn Iterator) -> StdVector { + let items: Vec = iter.collect(); + let count = items.len(); + let items_ptr = items.as_ptr(); + let vector = unsafe { + cpp!([items_ptr as "const item_t*", count as "size_t"] -> *mut c_void as "std::vector*" { + #if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L + const auto range = count == 0 + ? std::span() + : std::span(items_ptr, count); + return new std::vector(std::from_range, range); + #else + #error "The vector range constructor requires C++23 container range support" + #endif + }) + }; + StdVector(vector, PhantomData) + } + + fn len(&self) -> c_size_t { + let vector = self.0; + unsafe { + cpp!([vector as "const std::vector*"] -> c_size_t as "size_t" { + return vector->size(); + }) + } + } + + fn erase_one(&mut self, position: c_size_t) -> c_size_t { + assert!( + position < self.len(), + "erase position must be less than the vector length" + ); + let vector = self.0; + unsafe { + cpp!([vector as "std::vector*", position as "size_t"] -> c_size_t as "size_t" { + const auto next = vector->erase(vector->cbegin() + position); + return static_cast(next - vector->begin()); + }) + } + } + + fn erase_range(&mut self, first: c_size_t, last: c_size_t) -> c_size_t { + assert!(first <= last, "erase range start must not exceed its end"); + let len = self.len(); + assert!( + last <= len, + "erase range end must not exceed the vector length" + ); + let vector = self.0; + unsafe { + cpp!([vector as "std::vector*", first as "size_t", last as "size_t", len as "size_t"] -> c_size_t as "size_t" { + // Avoid arithmetic on an empty vector's potentially null iterators. + if (len == 0) { + vector->erase(vector->cbegin(), vector->cend()); + return 0; + } + const auto next = vector->erase(vector->cbegin() + first, vector->cbegin() + last); + return static_cast(next - vector->begin()); + }) + } + } + + fn resize_default(&mut self, count: c_size_t) { + let vector = self.0; + unsafe { + cpp!([vector as "std::vector*", count as "size_t"] { + vector->resize(count); + }) + } + } + + fn resize_with(&mut self, count: c_size_t, value: T) { + let vector = self.0; + unsafe { + cpp!([vector as "std::vector*", count as "size_t", value as "item_t"] { + vector->resize(count, value); + }) + } + } + + fn from_slice(items: &[T]) -> StdVector { let count = items.len(); let items = items.as_ptr(); let vector = unsafe { - cpp!([items as "const int*", count as "size_t"] -> *mut c_void as "std::vector*" { + cpp!([items as "const item_t*", count as "size_t"] -> *mut c_void as "std::vector*" { // An empty Rust slice may have a dangling pointer. Use a real C++ object // for its empty iterator range, avoiding arithmetic on that pointer. - const int empty = 0; - const int* first = count == 0 ? &empty : items; - return new std::vector(first, first + count); + const item_t empty = 0; + const item_t* first = count == 0 ? &empty : items; + return new std::vector(first, first + count); }) }; - StdVector(vector) + StdVector(vector, PhantomData) } } // Constructor coverage: https://eel.is/c++draft/vector.overview // Allocator variants are excluded by the task instructions. -// Deferred constructors (not proven inexpressible): -// - Copy: needs an overload borrowing another wrapper and independent-copy tests. -// - Move: needs ownership and moved-from-state tests; moving the Rust wrapper does -// not invoke the C++ move constructor. -// - Initializer-list: needs a distinct input representation; the slice overload -// calls the iterator-pair constructor instead. -// - C++23 from_range: needs a range bridge and a distinct tag or argument type. // cpp_build must see cpp! calls directly in the source, outside overload! expansion. overload! { @@ -96,12 +272,72 @@ overload! { /// /// ### Ergonomics /// - /// Counts use `c_size_t`, while elements use `c_int`. Callers can use explicit + /// Counts use `c_size_t`, while elements use `T`. Callers can use explicit /// argument types to select this overload. - pub fn new(count: c_size_t, value: c_int) -> StdVector { + pub fn new(count: c_size_t, value: T) -> StdVector { StdVector::repeat_with(count, value) } + /// Construct a vector containing the single listed element. + /// + /// ### Limitations + /// + /// Initializer-list overloads support only one, two, or three elements. + /// They use C++ braced initialization, unlike the slice overload's iterator pair. + /// If `T` becomes `c_size_t`, the one- and two-element signatures collide with + /// the count and fill signatures; a distinct wrapper or tag would be needed. + /// + /// ### Ergonomics + /// + /// Use explicitly typed `T` arguments for listed elements and `c_size_t` for + /// counts to distinguish initializer-list construction from count and fill. + pub fn new(a: T) -> StdVector { + StdVector::from_initializer_list_1(a) + } + + /// Construct a vector containing two listed elements, in argument order. + pub fn new(a: T, b: T) -> StdVector { + StdVector::from_initializer_list_2(a, b) + } + + /// Construct a vector containing three listed elements, in argument order. + pub fn new(a: T, b: T, c: T) -> StdVector { + StdVector::from_initializer_list_3(a, b, c) + } + + /// Move another vector's contents, leaving its object valid but in an unspecified state. + pub fn new(other: &mut StdVector) -> StdVector { + StdVector::move_from(other) + } + + /// Copy another vector into independently owned storage. + pub fn new(other: &StdVector) -> StdVector { + StdVector::copy_from(other) + } + + /// Construct from the iterator's remaining items using C++23 `std::from_range`. + /// + /// ### Limitations + /// + /// Eagerly collects items into a temporary Rust `Vec` until the first `None`, + /// then copies them into the C++ vector through a `std::span`. + /// The span borrows the temporary buffer only for the duration of construction. + /// Empty input uses a default-constructed span without accessing the Rust pointer. + /// This needs extra buffer storage and does not support infinite input. + /// It exercises a contiguous, sized C++ range, not a single-pass input range. + /// The existing element-representation, ZST, and C++ exception limitations apply. + /// Rust collection may fail or the iterator may panic before entering C++. + /// Requires C++23 standard-library container range support; there is no fallback + /// to the iterator-pair constructor. + /// + /// ### Ergonomics + /// + /// Uses an iterator argument instead of exposing C++'s `std::from_range` tag. + /// Callers can explicitly coerce to `&mut dyn Iterator` to select it. + pub fn new(iter: &mut dyn Iterator) -> StdVector { + StdVector::from_iter(iter) + } + /// Copy a slice using the C++ iterator-pair constructor. /// /// ### Limitations @@ -114,9 +350,88 @@ overload! { /// Arrays must be converted to slices explicitly. A possible improvement is /// to accept array references directly, avoiding that conversion. This needs /// investigation into the macro's support for array lengths and overload selection. - pub fn new(items: &[c_int]) -> StdVector { + pub fn new(items: &[T]) -> StdVector { StdVector::from_slice(items) } + + } +} + +overload! { + impl StdVector { + /// Resize to `count` elements, appending zeros when growing. + /// + /// ### Limitations + /// + /// Both resize overloads retain the existing allocation-exception limitation: + /// growth can throw, and an exception crossing the `extern "C"` boundary is + /// undefined behavior. Excessive counts can throw `std::length_error`. + /// Reallocation invalidates all element pointers, references, and iterators; + /// shrinking invalidates those referring to erased elements. The past-the-end + /// iterator is invalidated when the size changes. Shrinking does not reduce + /// capacity. The wrapper currently exposes no borrowed element references. + /// See [C++ vector capacity operations](https://eel.is/c++draft/vector.capacity). + /// + /// ### Ergonomics + /// + /// `count` is the final length, not the number of elements to append. + /// Argument count distinguishes default growth from growth with a supplied value. + pub fn resize(&mut self, count: c_size_t) { + self.resize_default(count); + } + + /// Resize to `count` elements, appending copies of `value` when growing. + /// + /// Existing elements are preserved unless truncated; `value` is unused when + /// shrinking or retaining the current size. The allocation and invalidation + /// limitations documented on the default-growth overload also apply here. + /// The fill value is passed by value in Rust, rather than by C++ const reference. + pub fn resize(&mut self, count: c_size_t, value: T) { + self.resize_with(count, value); + } + } +} + +overload! { + impl StdVector { + /// Erase the element at `position` and return the following element's new index. + /// + /// The returned index equals the new length when no following element remains. + /// + /// ### Panics + /// + /// Panics before entering C++ if `position >= len`. + /// + /// ### Limitations + /// + /// Both erase overloads adapt C++ iterator arguments and results into indices; + /// they do not exercise passing C++ iterator wrappers across the boundary. + /// Returned indices describe the vector immediately after deletion and do not + /// track elements across later mutations. C++ invalidates iterators and references + /// at or after the erased position. Later elements shift left; capacity is retained. + /// With the current `int` elements, erase does not allocate or throw. Types with + /// throwing assignment would require handling exceptions before crossing the FFI + /// boundary. See [C++ erase](https://eel.is/c++draft/vector.modifiers). + /// + /// ### Ergonomics + /// + /// Positions are zero-based and checked in release builds as well as debug builds. + pub fn erase(&mut self, position: c_size_t) -> c_size_t { + self.erase_one(position) + } + + /// Erase `[first, last)` and return the following element's new index. + /// + /// Empty ranges, including `[len, len)`, are valid. The result equals `first`, + /// or the new length when deletion reaches the end. The index-adaptation and + /// invalidation limitations of the single-element overload also apply here. + /// + /// ### Panics + /// + /// Panics before entering C++ if `first > last` or `last > len`. + pub fn erase(&mut self, first: c_size_t, last: c_size_t) -> c_size_t { + self.erase_range(first, last) + } } } @@ -126,7 +441,7 @@ impl Drop for StdVector { // Each constructor allocates a distinct vector. The wrapper cannot be copied, // and no accessor exposes its pointer, so this deletes it exactly once. unsafe { - cpp!([vector as "std::vector*"] { + cpp!([vector as "std::vector*"] { delete vector; }) } @@ -135,16 +450,25 @@ impl Drop for StdVector { #[cfg(test)] impl StdVector { - fn len(&self) -> c_size_t { + fn data_ptr(&self) -> *const T { + let vector = self.0 as *const c_void; + unsafe { + cpp!([vector as "const std::vector*"] -> *const T as "const item_t*" { + return vector->data(); + }) + } + } + + fn assign_repeated(&mut self, count: c_size_t, value: T) { let vector = self.0; unsafe { - cpp!([vector as "const std::vector*"] -> c_size_t as "size_t" { - return vector->size(); + cpp!([vector as "std::vector*", count as "size_t", value as "item_t"] { + vector->assign(count, value); }) } } - fn get(&self, index: c_size_t) -> Option { + fn get(&self, index: c_size_t) -> Option { if index >= self.len() { return None; } @@ -152,7 +476,7 @@ impl StdVector { // The bounds check precedes the read; returning a copy avoids exposing a // reference into C++ storage with a Rust lifetime. Some(unsafe { - cpp!([vector as "const std::vector*", index as "size_t"] -> c_int as "int" { + cpp!([vector as "const std::vector*", index as "size_t"] -> T as "item_t" { return (*vector)[index]; }) }) @@ -163,7 +487,7 @@ impl StdVector { mod tests { use super::*; - fn assert_contents(vector: &StdVector, expected: &[c_int]) { + fn assert_contents(vector: &StdVector, expected: &[T]) { assert_eq!(vector.len(), expected.len()); for (index, value) in expected.iter().enumerate() { assert_eq!(vector.get(index), Some(*value)); @@ -171,6 +495,270 @@ mod tests { assert_eq!(vector.get(expected.len()), None); } + #[test] + fn erase_single_positions_return_following_index() { + for (position, expected) in [(0_usize, [20, 30]), (1, [10, 30]), (2, [10, 20])] { + let mut vector: StdVector = StdVector::new(&[10 as T, 20, 30][..]); + assert_eq!(vector.erase(position), position); + assert_contents(&vector, &expected); + } + let mut vector: StdVector = StdVector::new(7 as T); + assert_eq!(vector.erase(0_usize), 0); + assert_contents(&vector, &[]); + } + + #[test] + fn erase_ranges_preserve_remaining_elements_and_return_index() { + let cases: &[(usize, usize, &[T])] = &[ + (0, 2, &[30, 40]), + (1, 3, &[10, 40]), + (2, 4, &[10, 20]), + (0, 4, &[]), + (2, 2, &[10, 20, 30, 40]), + (4, 4, &[10, 20, 30, 40]), + ]; + for &(first, last, expected) in cases { + let mut vector: StdVector = StdVector::new(&[10 as T, 20, 30, 40][..]); + assert_eq!(vector.erase(first, last), first); + assert_contents(&vector, expected); + } + let mut empty: StdVector = StdVector::new(); + assert_eq!(empty.erase(0_usize, 0_usize), 0); + assert_contents(&empty, &[]); + } + + #[test] + fn erase_rejects_invalid_positions_without_changing_contents() { + for position in [3_usize, 4, usize::MAX] { + let mut vector: StdVector = StdVector::new(&[10 as T, 20, 30][..]); + let result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| vector.erase(position))); + assert!(result.is_err()); + assert_contents(&vector, &[10, 20, 30]); + } + let mut empty: StdVector = StdVector::new(); + let result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| empty.erase(0_usize))); + assert!(result.is_err()); + assert_contents(&empty, &[]); + } + + #[test] + fn erase_rejects_invalid_ranges_without_changing_contents() { + for (first, last) in [(2_usize, 1_usize), (0, 4), (4, 4), (0, usize::MAX)] { + let mut vector: StdVector = StdVector::new(&[10 as T, 20, 30][..]); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + vector.erase(first, last) + })); + assert!(result.is_err()); + assert_contents(&vector, &[10, 20, 30]); + } + } + + #[test] + fn resize_overloads_grow_with_distinct_values() { + let mut defaulted: StdVector = StdVector::new(&[2 as T, 5][..]); + let mut filled: StdVector = StdVector::new(&defaulted); + defaulted.resize(4_usize); + filled.resize(4_usize, -7 as T); + assert_contents(&defaulted, &[2, 5, 0, 0]); + assert_contents(&filled, &[2, 5, -7, -7]); + } + + #[test] + fn resize_overloads_shrink_preserving_prefix() { + let mut defaulted: StdVector = StdVector::new(&[2 as T, 5, 9][..]); + let mut filled: StdVector = StdVector::new(&defaulted); + defaulted.resize(2_usize); + filled.resize(2_usize, -7 as T); + assert_contents(&defaulted, &[2, 5]); + assert_contents(&filled, &[2, 5]); + } + + #[test] + fn resize_overloads_preserve_unchanged_size() { + let mut defaulted: StdVector = StdVector::new(&[2 as T, 5][..]); + let mut filled: StdVector = StdVector::new(&defaulted); + defaulted.resize(2_usize); + filled.resize(2_usize, -7 as T); + assert_contents(&defaulted, &[2, 5]); + assert_contents(&filled, &[2, 5]); + } + + #[test] + fn resize_overloads_accept_zero_and_regrow() { + let mut defaulted: StdVector = StdVector::new(2_usize, 5 as T); + let mut filled: StdVector = StdVector::new(&defaulted); + defaulted.resize(0_usize); + filled.resize(0_usize, -7 as T); + assert_contents(&defaulted, &[]); + assert_contents(&filled, &[]); + defaulted.resize(0_usize); + filled.resize(0_usize, -7 as T); + assert_contents(&defaulted, &[]); + assert_contents(&filled, &[]); + defaulted.resize(2_usize); + filled.resize(2_usize, -7 as T); + assert_contents(&defaulted, &[0, 0]); + assert_contents(&filled, &[-7, -7]); + } + + #[test] + fn from_range_constructor_accepts_empty_iterator() { + let mut iter = std::iter::empty::(); + let vector: StdVector = StdVector::new(&mut iter as &mut dyn Iterator); + assert_contents(&vector, &[]); + } + + #[test] + fn from_range_constructor_collects_filtered_and_mapped_items() { + let mut iter = (0 as T..6) + .filter(|value| value % 2 == 0) + .map(|value| value * 3); + let vector: StdVector = StdVector::new(&mut iter as &mut dyn Iterator); + assert_contents(&vector, &[0, 6, 12]); + assert_eq!(iter.next(), None); + } + + #[test] + fn from_range_constructor_uses_only_remaining_items() { + let mut iter = [2 as T, 5, 9].into_iter(); + assert_eq!(iter.next(), Some(2)); + let vector: StdVector = StdVector::new(&mut iter as &mut dyn Iterator); + assert_contents(&vector, &[5, 9]); + assert_eq!(iter.next(), None); + } + + #[test] + fn from_range_constructor_owns_elements_after_source_is_dropped() { + let vector: StdVector = { + let mut source: Vec = vec![2, 5, 9]; + let vector: StdVector = { + let mut iter = source.iter().copied(); + StdVector::new(&mut iter as &mut dyn Iterator) + }; + source[1] = -1; + assert_contents(&vector, &[2, 5, 9]); + vector + }; + assert_contents(&vector, &[2, 5, 9]); + } + + #[test] + fn initializer_list_one_element_is_distinct_from_count() { + let listed: StdVector = StdVector::new(3 as T); + let counted: StdVector = StdVector::new(3_usize); + assert_contents(&listed, &[3]); + assert_contents(&counted, &[0, 0, 0]); + + let listed_zero: StdVector = StdVector::new(0 as T); + let empty: StdVector = StdVector::new(0_usize); + assert_contents(&listed_zero, &[0]); + assert_contents(&empty, &[]); + } + + #[test] + fn initializer_list_two_elements_is_distinct_from_fill() { + let listed: StdVector = StdVector::new(3 as T, 7 as T); + let filled: StdVector = StdVector::new(3_usize, 7 as T); + assert_contents(&listed, &[3, 7]); + assert_contents(&filled, &[7, 7, 7]); + } + + #[test] + fn initializer_list_three_elements_preserves_order_and_values() { + let listed: StdVector = StdVector::new(-2 as T, 0 as T, 9 as T); + assert_contents(&listed, &[-2, 0, 9]); + } + + #[test] + fn move_constructor_transfers_buffer_and_source_can_be_reused() { + let mut source: StdVector = StdVector::new(&[2 as T, 5, 9][..]); + let original_object = source.0; + let original_data = source.data_ptr(); + let moved: StdVector = StdVector::new(&mut source); + assert_eq!(source.0, original_object); + assert_ne!(source.0, moved.0); + assert_eq!(moved.data_ptr(), original_data); + assert_contents(&moved, &[2, 5, 9]); + + source.assign_repeated(2, -1 as T); + assert_contents(&source, &[-1, -1]); + assert_contents(&moved, &[2, 5, 9]); + drop(source); + assert_contents(&moved, &[2, 5, 9]); + } + + #[test] + fn move_constructor_source_survives_destination_drop() { + let mut source: StdVector = StdVector::new(3_usize, 7 as T); + let moved: StdVector = StdVector::new(&mut source); + assert_contents(&moved, &[7, 7, 7]); + drop(moved); + source.assign_repeated(2, 4 as T); + assert_contents(&source, &[4, 4]); + } + + #[test] + fn move_constructor_destination_survives_moved_from_source_drop() { + let mut source: StdVector = StdVector::new(2_usize, 6 as T); + let moved: StdVector = StdVector::new(&mut source); + drop(source); + assert_contents(&moved, &[6, 6]); + } + + #[test] + fn move_constructor_accepts_empty_vector() { + let mut source: StdVector = StdVector::new(); + let moved: StdVector = StdVector::new(&mut source); + assert_ne!(source.0, moved.0); + assert_contents(&moved, &[]); + source.assign_repeated(1, 8 as T); + assert_contents(&source, &[8]); + assert_contents(&moved, &[]); + } + + #[test] + fn copy_constructor_owns_independent_elements() { + let copied: StdVector = { + let source: StdVector = StdVector::new(&[2 as T, 5, 9][..]); + let copied: StdVector = StdVector::new(&source); + assert_ne!(source.0, copied.0); + assert_contents(&source, &[2, 5, 9]); + assert_contents(&copied, &[2, 5, 9]); + + let source_ptr = source.0; + unsafe { + cpp!([source_ptr as "std::vector*"] { + (*source_ptr)[1] = -1; + }) + }; + assert_contents(&source, &[2, -1, 9]); + assert_contents(&copied, &[2, 5, 9]); + copied + }; + assert_contents(&copied, &[2, 5, 9]); + } + + #[test] + fn copy_constructor_leaves_source_valid_after_copy_is_dropped() { + let source: StdVector = StdVector::new(3_usize, 7 as T); + let copied: StdVector = StdVector::new(&source); + assert_contents(&copied, &[7, 7, 7]); + drop(copied); + assert_contents(&source, &[7, 7, 7]); + } + + #[test] + fn copy_constructor_accepts_empty_vector() { + let source: StdVector = StdVector::new(); + let copied: StdVector = StdVector::new(&source); + assert_ne!(source.0, copied.0); + assert_contents(&source, &[]); + drop(source); + assert_contents(&copied, &[]); + } + #[test] fn empty_constructor() { let vector: StdVector = StdVector::new(); @@ -191,23 +779,23 @@ mod tests { #[test] fn fill_constructor_preserves_count_and_value_order() { - let vector: StdVector = StdVector::new(3_usize, 7 as c_int); + let vector: StdVector = StdVector::new(3_usize, 7 as T); assert_contents(&vector, &[7, 7, 7]); - let negative: StdVector = StdVector::new(2_usize, -5 as c_int); + let negative: StdVector = StdVector::new(2_usize, -5 as T); assert_contents(&negative, &[-5, -5]); } #[test] fn fill_constructor_accepts_zero_count() { - let vector: StdVector = StdVector::new(0_usize, 7 as c_int); + let vector: StdVector = StdVector::new(0_usize, 7 as T); assert_contents(&vector, &[]); } #[test] fn range_constructor_copies_the_selected_elements() { let vector: StdVector = { - let mut source: Vec = vec![99, 2, 5, 9, 100]; + let mut source: Vec = vec![99, 2, 5, 9, 100]; let vector: StdVector = StdVector::new(&source[1..4]); source[2] = -1; assert_contents(&vector, &[2, 5, 9]); @@ -219,11 +807,11 @@ mod tests { #[test] fn range_constructor_accepts_empty_slices() { - let empty: &[c_int] = &[]; + let empty: &[T] = &[]; let vector: StdVector = StdVector::new(empty); assert_contents(&vector, &[]); - let source: [c_int; 2] = [2, 5]; + let source: [T; 2] = [2, 5]; let vector: StdVector = StdVector::new(&source[2..]); assert_contents(&vector, &[]); } From b675ce1fb26fdc5cdbcc351bf213424e55547785 Mon Sep 17 00:00:00 2001 From: Ayuse Date: Fri, 23 Oct 2026 16:03:44 +0100 Subject: [PATCH 6/8] Add vector overload incompatibility examples --- cpp-overload-test/examples/stdcpp-vector.rs | 81 +++++++++++++++++++ .../examples/stdcpp-vector.stderr | 47 +++++++++++ 2 files changed, 128 insertions(+) create mode 100644 cpp-overload-test/examples/stdcpp-vector.rs create mode 100644 cpp-overload-test/examples/stdcpp-vector.stderr diff --git a/cpp-overload-test/examples/stdcpp-vector.rs b/cpp-overload-test/examples/stdcpp-vector.rs new file mode 100644 index 0000000..fefbf95 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-vector.rs @@ -0,0 +1,81 @@ +//! Demonstrate incompatible Rust translations of C++ `std::vector` overloads. +//! Expected compiler output is in `stdcpp-vector.stderr`; working overloads are in +//! `src/stdcpp/vector.rs`. This example uses Rust-only stand-ins because the crate's +//! `cpp!` build scans the library, not separately compiled examples. +//! +//! ### Incompatibilities +//! +//! Setting the element alias `T` to `c_size_t` makes the one-element initializer-list +//! signature identical to the count constructor, and the two-element signature +//! identical to the fill constructor. The generated argument-trait implementations +//! overlap. Explicit argument casts cannot distinguish identical types. +//! This collision comes from translating initializer lists into separate arguments: +//! C++ has a distinct `initializer_list` parameter and braced-initialization syntax. +//! +//! The macro also rejects `front(&self)` and `front(&mut self)` in one overload set. +//! Its single dispatch method must have one receiver kind, so it cannot directly +//! represent the const and non-const C++ accessor pair. +//! +//! ### Workarounds +//! +//! Give initializer-list input a distinct wrapper type, or use separate constructor +//! names for element lists and counts. Accepting slices instead is another API option, +//! but the existing slice bridge invokes the iterator-pair constructor and would not +//! demonstrate the initializer-list constructor. +//! +//! Use separate `front` and `front_mut` methods for the accessor pair. An alternative +//! worth testing is associated-function overloads with explicit `&Vector` and +//! `&mut Vector` arguments instead of receivers. A working borrowed accessor must +//! tie the returned reference's lifetime to the vector and prevent invalidating +//! mutations while that reference is borrowed. +//! +//! C++ signatures: . + +#![feature(splat, tuple_trait, c_size_t)] +#![allow(incomplete_features)] +#![allow(unused_braces)] + +use splat_overload::overload; +use std::ffi::c_size_t; + +type T = c_size_t; + +#[expect(dead_code, reason = "Incompatible overloads cannot be called")] +struct StdVectorT(Vec); + +overload! { + impl StdVectorT { + fn new(_count: c_size_t) -> StdVectorT { + todo!() + } + + fn new(_count: c_size_t, _value: T) -> StdVectorT { + todo!() + } + + fn new(_value: T) -> StdVectorT { + todo!() + } + + fn new(_a: T, _b: T) -> StdVectorT { + todo!() + } + } +} + +#[expect(dead_code, reason = "Incompatible overloads cannot be called")] +struct StdVectorFront(Vec); + +overload! { + impl StdVectorFront { + fn front(&self) -> Option<&std::ffi::c_int> { + self.0.first() + } + + fn front(&mut self) -> Option<&mut std::ffi::c_int> { + self.0.first_mut() + } + } +} + +fn main() {} diff --git a/cpp-overload-test/examples/stdcpp-vector.stderr b/cpp-overload-test/examples/stdcpp-vector.stderr new file mode 100644 index 0000000..2f5cfc8 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-vector.stderr @@ -0,0 +1,47 @@ +error: proc macro panicked + --> cpp-overload-test/examples/stdcpp-vector.rs:69:1 + | +69 | / overload! { +70 | | impl StdVectorFront { +71 | | fn front(&self) -> Option<&std::ffi::c_int> { +72 | | self.0.first() +... | +79 | | } + | |_^ + | + = help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got front: &self and front: &mut self + +error[E0119]: conflicting implementations of trait `NewArgs` for type `(usize,)` + --> cpp-overload-test/examples/stdcpp-vector.rs:46:1 + | +46 | / overload! { +47 | | impl StdVectorT { +48 | | fn new(_count: c_size_t) -> StdVectorT { +49 | | todo!() +... | +64 | | } + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `(usize,)` + | + = note: this error originates in the macro `overload` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0119]: conflicting implementations of trait `NewArgs` for type `(usize, usize)` + --> cpp-overload-test/examples/stdcpp-vector.rs:46:1 + | +46 | / overload! { +47 | | impl StdVectorT { +48 | | fn new(_count: c_size_t) -> StdVectorT { +49 | | todo!() +... | +64 | | } + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `(usize, usize)` + | + = note: this error originates in the macro `overload` (in Nightly builds, run with -Z macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0119`. +error: could not compile `cpp-overload-test` (example "stdcpp-vector") due to 3 previous errors From a518e82945033cbac1934be6841ed1b8ee364c86 Mon Sep 17 00:00:00 2001 From: Ayuse Date: Fri, 23 Oct 2026 16:03:44 +0100 Subject: [PATCH 7/8] Document vector overload compatibility results --- cpp-overloads-in-rust.md | 60 +++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/cpp-overloads-in-rust.md b/cpp-overloads-in-rust.md index dd22d16..3713b44 100644 --- a/cpp-overloads-in-rust.md +++ b/cpp-overloads-in-rust.md @@ -1,7 +1,6 @@ - # C++ Overloads in Rust: Experimental Outcomes -***This document is an initial, partially reviewed draft*** +_**This document is an initial, partially reviewed draft**_ This table shows how well C++ standard library overloads can be represented in Rust. @@ -10,24 +9,25 @@ overloading macro: they are not Rust language design decisions. See the Limitations section below the table for more details. -*Key:*
+_Key:_
⛔ Conflicting Overloads
🦀 Rust Compiler Limitations
⚙️ Overload Macro Limitations
🌗 Partial Support or Poor Ergonomics
✅ Works in Rust
? Needs Analysis
-*blank* No C++ Overloads
+_blank_ No C++ Overloads
| C++ Standard Library Class | Constructors | Accessors | Modifiers | Transforms | | :------------------------- | :----------- | :-------- | :--------- | :--------- | -| *Free Functions* | Constructors | Accessors | Modifiers | Transforms | -| TODO | | ? | ? | ? | -| *Non-Template Classes* | Constructors | Accessors | Modifiers | Transforms | -| TODO | ? | ? | ? | ? | -| *Template Classes* | Constructors | Accessors | Modifiers | Transforms | +| _Free Functions_ | Constructors | Accessors | Modifiers | Transforms | +| TODO | | ? | ? | ? | +| _Non-Template Classes_ | Constructors | Accessors | Modifiers | Transforms | +| TODO | ? | ? | ? | ? | +| _Template Classes_ | Constructors | Accessors | Modifiers | Transforms | | [forward_list] | ✅ | 🌗⚙️[^1] | 🌗⚙️?[^2] | ✅ | -| TODO | ? | ? | ? | ? | +| [vector] | 🌗⛔[^3] | ⚙️?[^4] | 🌗?[^5] | ?[^6] | +| TODO | ? | ? | ? | ? | Constructors: Create an instance of the class, called as `Class::Class(...)`
Accessors: Get information about the class, called as `instance->get(...)`
@@ -35,10 +35,34 @@ Modifiers: Add or change the items in a list class, called as `instance->put(... Transforms: Rearrange the items in a list class, called as `instance->sort(...)`
[forward_list]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/forward_list.rs +[vector]: cpp-overload-test/src/stdcpp/vector.rs +[vector-failures]: cpp-overload-test/examples/stdcpp-vector.rs +[vector-diagnostics]: cpp-overload-test/examples/stdcpp-vector.stderr [^1]: Rust and C++ iterators have different semantics, using adapter types is an ergonomic workaround. + [^2]: `insert_after` and `splice_after` still need to be analysed. +[^3]: + Constructor families work together for `T = c_int`, excluding allocator variants. + Initializer-list input is limited to one, two, or three elements; `from_range` uses a + buffered Rust iterator and C++ span. With `T = c_size_t`, the translated one- and + two-element initializer-list signatures conflict with count and fill constructors. + These are Rust translation conflicts, not conflicting C++ signatures; see the + [failing example][vector-failures] and [compiler diagnostics][vector-diagnostics]. + +[^4]: + The tested `front(&self)` / `front(&mut self)` pair is rejected by the macro's + receiver restriction. Other accessor overloads were not independently tested. + Separate method names are a workaround; see [vector failures][vector-failures]. + +[^5]: + Both `resize` overloads and both `erase` overloads pass tests. `erase` adapts C++ + iterator arguments and results into checked indices. Other modifier overload sets, + including `insert`, still need analysis. + +[^6]: Vector transforms have not been analysed; this does not assert an absence of overloads. + ## Limitations This section summarises common issues discovered during the analysis, and documents less @@ -69,18 +93,26 @@ Some overloads are confusing or hard to use, so we might not want to represent t - in generic list overloads, `new(count: usize)` and `new(item: T)` are easily confused if `T` is an integer (and they conflict if `T` is `usize`, which means it can't be fully generic) - this can be resolved by skipping overloads for initializer lists + - the two-element form `new(a: T, b: T)` also conflicts with + `new(count: usize, value: T)` when `T` is `usize` + - a distinct initializer-list wrapper or separate constructor names can distinguish + these cases; explicit casts cannot distinguish identical signatures - `&dyn Iterator` overloads require a type cast - this could be resolved with improvements to the macro, or with an iterator adapter type Type inference doesn't work on some overloads: this is likely a macro or compiler experiment limitation. +The vector experiment uses checked indices for `erase` and buffers Rust iterator input into +a C++ span for `from_range`. These adapters preserve useful behavior but do not exercise +passing C++ iterator wrappers or a single-pass C++ input range through the Rust interface. + Some C++ overloads [use a tag argument][cpp-tag-arg] for [overload disambiguation][cpp-tag-type], a strategy which could be adopted in Rust APIs. But in some cases, the Rust overload [might not need disambiguation][rust-no-disambig]. [cpp-tag-arg]: https://en.cppreference.com/cpp/container/forward_list/forward_list [cpp-tag-type]: https://en.cppreference.com/cpp/ranges/from_range -[rust-no-disambig]: https://github.com/rustfoundation/overloading-macros/blob/52c3f3aa5e4e6f11d71d2d832fce733f00d0b087/cpp-overload-test/src/stdcpp/forward_list.rs#L89 +[rust-no-disambig]: https://github.com/rustfoundation/overloading-macros/blob/52c3f3aa5e4e6f11d71d2d832fce733f00d0b087/cpp-overload-test/src/stdcpp/forward_list.rs#L89 ### Scope @@ -99,8 +131,10 @@ These overloads have no equivalent in Rust: - assignment functions, assignment operators, and `swap`: there is no "type move method" in Rust (it uses `memcpy` for assignment/swaps) - legacy iterator pointer-like methods for various positions (`before_begin`/`begin`/`end`) -- initializer list methods: these are also a common source of overload conflicts for C++ templated - lists + +Rust has no direct equivalent to C++ initializer-list parameters and braced-initialization +syntax. The vector experiment tests fixed one-, two-, and three-element constructor adapters +and documents their overload conflicts; it does not provide general initializer-list support. #### Equivalences & Supersets From 9934cc90eb6a85c9961432ecd4e137974b912282 Mon Sep 17 00:00:00 2001 From: Ayuse Date: Tue, 27 Oct 2026 12:16:19 +0100 Subject: [PATCH 8/8] Address vector documentation and diagnostic review feedback --- cpp-overload-test/examples/stdcpp-vector.rs | 7 ++ .../examples/stdcpp-vector.stderr | 36 +++--- cpp-overload-test/src/stdcpp/vector.rs | 106 +++++++++--------- cpp-overloads-in-rust.md | 36 +++--- 4 files changed, 92 insertions(+), 93 deletions(-) diff --git a/cpp-overload-test/examples/stdcpp-vector.rs b/cpp-overload-test/examples/stdcpp-vector.rs index fefbf95..3faeb53 100644 --- a/cpp-overload-test/examples/stdcpp-vector.rs +++ b/cpp-overload-test/examples/stdcpp-vector.rs @@ -16,6 +16,13 @@ //! Its single dispatch method must have one receiver kind, so it cannot directly //! represent the const and non-const C++ accessor pair. //! +//! ### Macro ergonomics +//! +//! FIXME: The `(usize, usize)` conflict diagnostic points at the overload block +//! and displays the one-argument constructor instead of the conflicting two-argument +//! constructors. Improve the macro's generated spans so the error identifies the +//! count-and-value and two-element initializer-list overloads. +//! //! ### Workarounds //! //! Give initializer-list input a distinct wrapper type, or use separate constructor diff --git a/cpp-overload-test/examples/stdcpp-vector.stderr b/cpp-overload-test/examples/stdcpp-vector.stderr index 2f5cfc8..90d57a1 100644 --- a/cpp-overload-test/examples/stdcpp-vector.stderr +++ b/cpp-overload-test/examples/stdcpp-vector.stderr @@ -1,25 +1,25 @@ error: proc macro panicked - --> cpp-overload-test/examples/stdcpp-vector.rs:69:1 + --> cpp-overload-test/examples/stdcpp-vector.rs:76:1 | -69 | / overload! { -70 | | impl StdVectorFront { -71 | | fn front(&self) -> Option<&std::ffi::c_int> { -72 | | self.0.first() +76 | / overload! { +77 | | impl StdVectorFront { +78 | | fn front(&self) -> Option<&std::ffi::c_int> { +79 | | self.0.first() ... | -79 | | } +86 | | } | |_^ | = help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got front: &self and front: &mut self error[E0119]: conflicting implementations of trait `NewArgs` for type `(usize,)` - --> cpp-overload-test/examples/stdcpp-vector.rs:46:1 + --> cpp-overload-test/examples/stdcpp-vector.rs:53:1 | -46 | / overload! { -47 | | impl StdVectorT { -48 | | fn new(_count: c_size_t) -> StdVectorT { -49 | | todo!() +53 | / overload! { +54 | | impl StdVectorT { +55 | | fn new(_count: c_size_t) -> StdVectorT { +56 | | todo!() ... | -64 | | } +71 | | } | | ^ | | | | |_first implementation here @@ -28,14 +28,14 @@ error[E0119]: conflicting implementations of trait `NewArgs` for typ = note: this error originates in the macro `overload` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait `NewArgs` for type `(usize, usize)` - --> cpp-overload-test/examples/stdcpp-vector.rs:46:1 + --> cpp-overload-test/examples/stdcpp-vector.rs:53:1 | -46 | / overload! { -47 | | impl StdVectorT { -48 | | fn new(_count: c_size_t) -> StdVectorT { -49 | | todo!() +53 | / overload! { +54 | | impl StdVectorT { +55 | | fn new(_count: c_size_t) -> StdVectorT { +56 | | todo!() ... | -64 | | } +71 | | } | | ^ | | | | |_first implementation here diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs index a3c283a..03b56da 100644 --- a/cpp-overload-test/src/stdcpp/vector.rs +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -1,56 +1,4 @@ //! Check whether C++ `std::vector` overloads can be represented in Rust. -//! -//! ### Limitations -//! -//! This experiment fixes the element type to C++ `int`; it does not test generic overloads. -//! -//! Rust zero-sized element types (ZSTs), such as `()`, are not supported. A nonempty -//! Rust slice of ZSTs has a logical element count but needs no element storage, and -//! pointer offsets do not advance its address. References must still be non-null -//! and suitably aligned. In C++, even an empty class has a nonzero `sizeof`, and its -//! array elements occupy storage. Empty base classes and `[[no_unique_address]]` -//! members can occupy no additional storage, but these exceptions do not make -//! ordinary vector elements zero-sized. -//! -//! The slice constructor passes the Rust element pointer to C++ as `const item_t*` -//! and forms the iterator range from `first` to `first + count`. This requires -//! compatible Rust and C++ element representations and valid backing storage for -//! that range. A Rust ZST slice cannot be reinterpreted as an array of nonzero-sized -//! C++ objects. The local C++ fallback object handles only `count == 0`; a nonempty -//! ZST slice would bypass it. `PhantomData` records ownership but supplies no -//! element storage. Supporting ZSTs would require a separate conversion strategy, -//! rather than simply changing the `T` and `item_t` aliases. -//! -//! See [Rust ZSTs], [Rust Vec guarantees], [C++ object sizes], and [C++ sizeof]. -//! -//! Each constructor uses throwing C++ `new` to allocate the vector object, even for an -//! empty vector. Allocation failure is reported by throwing `std::bad_alloc`, rather -//! than returning a null pointer. Allocating the vector's element storage can also -//! throw `std::bad_alloc`. Excessive element counts can cause other exceptions; for -//! example, libc++ throws `std::length_error` when a count exceeds `max_size()`. -//! -//! Neither these constructors nor the wrappers generated by rust-cpp 0.5.11 catch -//! C++ exceptions. The generated Rust declarations use `extern "C"`, which does not -//! permit unwinding. If a C++ exception unwinds across this boundary into Rust, the -//! behavior is undefined; a Rust panic or clean process abort is not guaranteed. -//! These constructors therefore do not safely handle allocation failures. -//! -//! Safe handling would require catching exceptions on the C++ side before they cross -//! the boundary, then returning an explicit error indication or deliberately -//! terminating the process. Using `new (std::nothrow)` alone would not handle exceptions -//! thrown by the vector constructor while allocating element storage. -//! -//! References: [C++ allocation functions], [the default allocator], [libc++ vector], -//! and [Rust FFI unwinding]. -//! -//! [C++ allocation functions]: https://eel.is/c++draft/new.delete.single -//! [the default allocator]: https://eel.is/c++draft/allocator.members -//! [libc++ vector]: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__vector/vector.h -//! [Rust FFI unwinding]: https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding -//! [Rust ZSTs]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts -//! [Rust Vec guarantees]: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees -//! [C++ object sizes]: https://eel.is/c++draft/intro.object -//! [C++ sizeof]: https://eel.is/c++draft/expr.sizeof #![allow(unused_braces, reason = "Generated by overload! macro")] @@ -59,6 +7,7 @@ use splat_overload::overload; use std::ffi::{c_size_t, c_void}; use std::marker::PhantomData; +/// This experiment fixes the element type to C++ `int`; it does not test generic overloads. pub type T = std::ffi::c_int; cpp! {{ @@ -72,6 +21,23 @@ cpp! {{ }} /// Owns a C++ `std::vector`; its pointer stays private so callers cannot replace it. +/// +/// ### Limitations +/// +/// Rust zero-sized element types (ZSTs), such as `()`, are not supported. A nonempty +/// Rust slice of ZSTs has a logical element count but needs no element storage, and +/// pointer offsets do not advance its address. References must still be non-null +/// and suitably aligned. In C++, even an empty class has a nonzero `sizeof`, and its +/// array elements occupy storage. Empty base classes and `[[no_unique_address]]` +/// members can occupy no additional storage, but these exceptions do not make +/// ordinary vector elements zero-sized. +/// +/// See [Rust ZSTs], [Rust Vec guarantees], [C++ object sizes], and [C++ sizeof]. +/// +/// [Rust ZSTs]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts +/// [Rust Vec guarantees]: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees +/// [C++ object sizes]: https://eel.is/c++draft/intro.object +/// [C++ sizeof]: https://eel.is/c++draft/expr.sizeof pub struct StdVector(*mut c_void, PhantomData); #[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] @@ -254,6 +220,33 @@ impl StdVector { overload! { impl StdVector { /// Construct an empty vector. + /// + /// ### Limitations + /// + /// Each constructor uses throwing C++ `new` to allocate the vector object, even for an + /// empty vector. Allocation failure is reported by throwing `std::bad_alloc`, rather + /// than returning a null pointer. Allocating the vector's element storage can also + /// throw `std::bad_alloc`. Excessive element counts can cause other exceptions; for + /// example, libc++ throws `std::length_error` when a count exceeds `max_size()`. + /// + /// Neither these constructors nor the wrappers generated by rust-cpp 0.5.11 catch + /// C++ exceptions. The generated Rust declarations use `extern "C"`, which does not + /// permit unwinding. If a C++ exception unwinds across this boundary into Rust, the + /// behavior is undefined; a Rust panic or clean process abort is not guaranteed. + /// These constructors therefore do not safely handle allocation failures. + /// + /// Safe handling would require catching exceptions on the C++ side before they cross + /// the boundary, then returning an explicit error indication or deliberately + /// terminating the process. Using `new (std::nothrow)` alone would not handle exceptions + /// thrown by the vector constructor while allocating element storage. + /// + /// References: [C++ allocation functions], [the default allocator], [libc++ vector], + /// and [Rust FFI unwinding]. + /// + /// [C++ allocation functions]: https://eel.is/c++draft/new.delete.single + /// [the default allocator]: https://eel.is/c++draft/allocator.members + /// [libc++ vector]: https://github.com/llvm/llvm-project/blob/main/libcxx/include/__vector/vector.h + /// [Rust FFI unwinding]: https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding pub fn new() -> StdVector { StdVector::empty() } @@ -345,6 +338,15 @@ overload! { /// Represents the iterator-pair constructor using contiguous input, rather /// than exposing arbitrary C++ iterators. /// + /// The slice constructor passes the Rust element pointer to C++ as `const item_t*` + /// and forms the iterator range from `first` to `first + count`. This requires + /// compatible Rust and C++ element representations and valid backing storage for + /// that range. A Rust ZST slice cannot be reinterpreted as an array of nonzero-sized + /// C++ objects. The local C++ fallback object handles only `count == 0`; a nonempty + /// ZST slice would bypass it. `PhantomData` records ownership but supplies no + /// element storage. Supporting ZSTs would require a separate conversion strategy, + /// rather than simply changing the `T` and `item_t` aliases. + /// /// ### Ergonomics /// /// Arrays must be converted to slices explicitly. A possible improvement is diff --git a/cpp-overloads-in-rust.md b/cpp-overloads-in-rust.md index 3713b44..ef62935 100644 --- a/cpp-overloads-in-rust.md +++ b/cpp-overloads-in-rust.md @@ -1,6 +1,7 @@ + # C++ Overloads in Rust: Experimental Outcomes -_**This document is an initial, partially reviewed draft**_ +***This document is an initial, partially reviewed draft*** This table shows how well C++ standard library overloads can be represented in Rust. @@ -9,25 +10,25 @@ overloading macro: they are not Rust language design decisions. See the Limitations section below the table for more details. -_Key:_
+*Key:*
⛔ Conflicting Overloads
🦀 Rust Compiler Limitations
⚙️ Overload Macro Limitations
🌗 Partial Support or Poor Ergonomics
✅ Works in Rust
? Needs Analysis
-_blank_ No C++ Overloads
+*blank* No C++ Overloads
| C++ Standard Library Class | Constructors | Accessors | Modifiers | Transforms | | :------------------------- | :----------- | :-------- | :--------- | :--------- | -| _Free Functions_ | Constructors | Accessors | Modifiers | Transforms | -| TODO | | ? | ? | ? | -| _Non-Template Classes_ | Constructors | Accessors | Modifiers | Transforms | -| TODO | ? | ? | ? | ? | -| _Template Classes_ | Constructors | Accessors | Modifiers | Transforms | +| *Free Functions* | Constructors | Accessors | Modifiers | Transforms | +| TODO | | ? | ? | ? | +| *Non-Template Classes* | Constructors | Accessors | Modifiers | Transforms | +| TODO | ? | ? | ? | ? | +| *Template Classes* | Constructors | Accessors | Modifiers | Transforms | | [forward_list] | ✅ | 🌗⚙️[^1] | 🌗⚙️?[^2] | ✅ | | [vector] | 🌗⛔[^3] | ⚙️?[^4] | 🌗?[^5] | ?[^6] | -| TODO | ? | ? | ? | ? | +| TODO | ? | ? | ? | ? | Constructors: Create an instance of the class, called as `Class::Class(...)`
Accessors: Get information about the class, called as `instance->get(...)`
@@ -40,7 +41,6 @@ Transforms: Rearrange the items in a list class, called as `instance->sort(...)` [vector-diagnostics]: cpp-overload-test/examples/stdcpp-vector.stderr [^1]: Rust and C++ iterators have different semantics, using adapter types is an ergonomic workaround. - [^2]: `insert_after` and `splice_after` still need to be analysed. [^3]: @@ -93,26 +93,18 @@ Some overloads are confusing or hard to use, so we might not want to represent t - in generic list overloads, `new(count: usize)` and `new(item: T)` are easily confused if `T` is an integer (and they conflict if `T` is `usize`, which means it can't be fully generic) - this can be resolved by skipping overloads for initializer lists - - the two-element form `new(a: T, b: T)` also conflicts with - `new(count: usize, value: T)` when `T` is `usize` - - a distinct initializer-list wrapper or separate constructor names can distinguish - these cases; explicit casts cannot distinguish identical signatures - `&dyn Iterator` overloads require a type cast - this could be resolved with improvements to the macro, or with an iterator adapter type Type inference doesn't work on some overloads: this is likely a macro or compiler experiment limitation. -The vector experiment uses checked indices for `erase` and buffers Rust iterator input into -a C++ span for `from_range`. These adapters preserve useful behavior but do not exercise -passing C++ iterator wrappers or a single-pass C++ input range through the Rust interface. - Some C++ overloads [use a tag argument][cpp-tag-arg] for [overload disambiguation][cpp-tag-type], a strategy which could be adopted in Rust APIs. But in some cases, the Rust overload [might not need disambiguation][rust-no-disambig]. [cpp-tag-arg]: https://en.cppreference.com/cpp/container/forward_list/forward_list [cpp-tag-type]: https://en.cppreference.com/cpp/ranges/from_range -[rust-no-disambig]: https://github.com/rustfoundation/overloading-macros/blob/52c3f3aa5e4e6f11d71d2d832fce733f00d0b087/cpp-overload-test/src/stdcpp/forward_list.rs#L89 +[rust-no-disambig]: https://github.com/rustfoundation/overloading-macros/blob/52c3f3aa5e4e6f11d71d2d832fce733f00d0b087/cpp-overload-test/src/stdcpp/forward_list.rs#L89 ### Scope @@ -131,10 +123,8 @@ These overloads have no equivalent in Rust: - assignment functions, assignment operators, and `swap`: there is no "type move method" in Rust (it uses `memcpy` for assignment/swaps) - legacy iterator pointer-like methods for various positions (`before_begin`/`begin`/`end`) - -Rust has no direct equivalent to C++ initializer-list parameters and braced-initialization -syntax. The vector experiment tests fixed one-, two-, and three-element constructor adapters -and documents their overload conflicts; it does not provide general initializer-list support. +- initializer list methods: these are also a common source of overload conflicts for C++ templated + lists #### Equivalences & Supersets