diff --git a/cpp-overload-test/examples/stdcpp-vector.rs b/cpp-overload-test/examples/stdcpp-vector.rs new file mode 100644 index 0000000..3faeb53 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-vector.rs @@ -0,0 +1,88 @@ +//! 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. +//! +//! ### 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 +//! 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..90d57a1 --- /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:76:1 + | +76 | / overload! { +77 | | impl StdVectorFront { +78 | | fn front(&self) -> Option<&std::ffi::c_int> { +79 | | self.0.first() +... | +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:53:1 + | +53 | / overload! { +54 | | impl StdVectorT { +55 | | fn new(_count: c_size_t) -> StdVectorT { +56 | | todo!() +... | +71 | | } + | | ^ + | | | + | |_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:53:1 + | +53 | / overload! { +54 | | impl StdVectorT { +55 | | fn new(_count: c_size_t) -> StdVectorT { +56 | | todo!() +... | +71 | | } + | | ^ + | | | + | |_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 diff --git a/cpp-overload-test/src/lib.rs b/cpp-overload-test/src/lib.rs index 88ec235..1ff89cf 100644 --- a/cpp-overload-test/src/lib.rs +++ b/cpp-overload-test/src/lib.rs @@ -12,6 +12,7 @@ pub mod stdcpp { pub mod forward_list; pub mod hash; pub mod unordered_multiset; + pub mod vector; } // Checks that `build.rs` rebuilds changed `cpp!` code, see `rerun-check.sh`. diff --git a/cpp-overload-test/src/stdcpp/vector.rs b/cpp-overload-test/src/stdcpp/vector.rs new file mode 100644 index 0000000..03b56da --- /dev/null +++ b/cpp-overload-test/src/stdcpp/vector.rs @@ -0,0 +1,820 @@ +//! Check whether C++ `std::vector` overloads can be represented in Rust. + +#![allow(unused_braces, reason = "Generated by overload! macro")] + +use cpp::cpp; +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! {{ + #include + #include + #include + #include + #include + + typedef int item_t; +}} + +/// 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"))] +impl StdVector { + fn empty() -> StdVector { + let vector = unsafe { + cpp!([] -> *mut c_void as "std::vector*" { + return new std::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); + }) + }; + 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, PhantomData) + } + + fn from_initializer_list_3(a: T, b: T, c: T) -> StdVector { + let vector = unsafe { + 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, PhantomData) + } + + 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 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 item_t empty = 0; + const item_t* first = count == 0 ? &empty : items; + return new std::vector(first, first + count); + }) + }; + StdVector(vector, PhantomData) + } +} + +// Constructor coverage: https://eel.is/c++draft/vector.overview +// Allocator variants are excluded by the task instructions. + +// cpp_build must see cpp! calls directly in the source, outside overload! expansion. +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() + } + + /// 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 `T`. Callers can use explicit + /// argument types to select this overload. + 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 + /// + /// 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 + /// 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: &[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) + } + } +} + +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 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 "std::vector*", count as "size_t", value as "item_t"] { + vector->assign(count, value); + }) + } + } + + 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"] -> T as "item_t" { + return (*vector)[index]; + }) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + 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)); + } + 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(); + 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 T); + assert_contents(&vector, &[7, 7, 7]); + + 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 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 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: &[T] = &[]; + let vector: StdVector = StdVector::new(empty); + assert_contents(&vector, &[]); + + let source: [T; 2] = [2, 5]; + let vector: StdVector = StdVector::new(&source[2..]); + assert_contents(&vector, &[]); + } +} diff --git a/cpp-overloads-in-rust.md b/cpp-overloads-in-rust.md index 0656300..190cd7a 100644 --- a/cpp-overloads-in-rust.md +++ b/cpp-overloads-in-rust.md @@ -29,6 +29,7 @@ See the Limitations section below the table for more details. | [forward_list] | ✅ | 🌗⚙️[^1] | 🌗⚙️?[^2] | ✅ | | [hash] | ✅ | ✅ | | | | [unordered_multiset] | ✅ | ✅⚙️[^3] | ✅ | ?[^4] | +| [vector] | 🌗⛔[^5] | ⚙️?[^6] | 🌗?[^7] | ?[^8] | | TODO | ? | ? | ? | ? | Constructors: Create an instance of the class, called as `Class::Class(...)`
@@ -39,12 +40,35 @@ 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 [hash]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/hash.rs [unordered_multiset]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/unordered_multiset.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]: The overload macro can't overload on `&self` vs `&mut self`, which limits representing some accessor overloads. [^4]: `extract`, `merge`, and `erase_if` still need analysis. These are C++17/C++20 node handle and non-member function overloads. +[^5]: + 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]. + +[^6]: + 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]. + +[^7]: + 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. + +[^8]: 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