diff --git a/cpp-overload-test/examples/stdcpp-multimap.rs b/cpp-overload-test/examples/stdcpp-multimap.rs new file mode 100644 index 0000000..0369fe4 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-multimap.rs @@ -0,0 +1,109 @@ +//! Test C++ [std::multimap][multimap] overloads that can't be represented in Rust, +//! and record the macro diagnostics. +//! Error output is in [stdcpp-multimap.stderr]. +//! Working overloads are in [multimap.rs]. +//! +//! [multimap]: https://en.cppreference.com/w/cpp/container/multimap +//! [stdcpp-multimap.stderr]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/examples/stdcpp-multimap.stderr +//! [multimap.rs]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/multimap.rs +//! +//! The `cpp!` macro doesn't work here, because of technical limitations it can only run one build +//! per crate. (Examples and binaries are built separately from `lib.rs`.) +//! To add C++ code here, use /* ... */ code comment blocks, or just skip it. + +#![feature(splat, tuple_trait, c_size_t)] +#![allow(incomplete_features)] +#![allow(unused_braces)] + +use splat_overload::overload; +use std::ffi::{c_int, c_size_t}; + +/// Workaround for missing generic support in the `overload!` macro. +type K = c_int; + +/// Workaround for missing generic support in the `overload!` macro. +type V = c_int; + +/// A fake multimap wrapper used to test overload resolution. +/// The C++ container is represented by a Rust vector for type checking. +#[expect(dead_code, reason = "Incompatible overloads can't be used")] +struct StdMultimapA(Vec<(K, V)>); + +// Accessors: `find(...)` +overload! { + impl StdMultimapA { + /// Find an element with the key, from a shared reference. + /// + /// ### Incompatibilities + /// + /// The `overload!` macro can't overload on `&self` vs `&mut self`, + /// because all overloads must have the same receiver kind. + /// + /// This also affects other const and non-const accessors, such as + /// `lower_bound`, `upper_bound`, `equal_range`, `begin` and `end`. + fn find(&self, _key: K) -> Option<&(K, V)> { + todo!() + } + + /// Find an element with the key, from a mutable reference. + fn find(&mut self, _key: K) -> Option<&mut (K, V)> { + todo!() + } + } +} + +/// A wrapper struct to demonstrate key and iterator-position collisions. +#[expect(dead_code, reason = "Incompatible overloads can't be used")] +struct StdMultimapB(Vec<(K, V)>); +struct StdMultimapIter; + +/// Workaround for missing generic support in the `overload!` macro. +/// Models a C++ iterator as an integer position. +type Pos = K; + +// Modifiers: `erase(...)` +overload! { + impl StdMultimapB { + /// Erase every element whose key equals `key`. + fn erase(&mut self, _key: K) -> c_size_t { + todo!() + } + + /// Erase the element at `pos`. + /// + /// ### Incompatibilities + /// + /// This overload has the same types as `erase(key)` when the iterator + /// is modelled as an integer, but different semantics: + /// - `erase(key)` removes every element with that key. + /// - `erase(pos)` removes one element. + fn erase(&mut self, _pos: Pos) -> StdMultimapIter { + todo!() + } + } +} + +/// A wrapper struct to demonstrate the workaround for different receiver kinds. +struct StdMultimapC(Vec<(K, V)>); + +// Workaround: associated functions with an explicit `this` argument. +overload! { + impl StdMultimapC { + /// Find an element with the key, from a shared reference. + /// + /// ### Workaround + /// + /// Uses an associated function instead of a method, with `&'static` + /// to avoid generic lifetimes. This is less ergonomic than a normal method. + fn find(_this: &'static StdMultimapC, _key: K) -> Option<&'static (K, V)> { + todo!() + } + + /// Find an element with the key, from a mutable reference. + fn find(_this: &'static mut StdMultimapC, _key: K) -> Option<&'static mut (K, V)> { + todo!() + } + } +} + +pub fn main() {} diff --git a/cpp-overload-test/examples/stdcpp-multimap.stderr b/cpp-overload-test/examples/stdcpp-multimap.stderr new file mode 100644 index 0000000..92303ec --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-multimap.stderr @@ -0,0 +1,31 @@ +error: proc macro panicked + --> cpp-overload-test/examples/stdcpp-multimap.rs:33:1 + | +33 | / overload! { +34 | | impl StdMultimapA { +35 | | /// Find an element with the key, from a shared reference. +36 | | /// +... | +53 | | } + | |_^ + | + = help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got find: &self and find: &mut self + +error[E0119]: conflicting implementations of trait `EraseArgs` for type `(i32,)` + --> cpp-overload-test/examples/stdcpp-multimap.rs:65:1 + | +65 | / overload! { +66 | | impl StdMultimapB { +67 | | /// Erase every element whose key equals `key`. +68 | | fn erase(&mut self, _key: K) -> c_size_t { +... | +84 | | } + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `(i32,)` + | + = 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-multimap") due to 2 previous errors diff --git a/cpp-overload-test/src/lib.rs b/cpp-overload-test/src/lib.rs index f6b1a92..7a9f7d6 100644 --- a/cpp-overload-test/src/lib.rs +++ b/cpp-overload-test/src/lib.rs @@ -11,6 +11,7 @@ pub mod stdcpp { // Add new modules for each type here, in alphabetical order. pub mod forward_list; pub mod hash; + pub mod multimap; pub mod unordered_map; pub mod unordered_multiset; } diff --git a/cpp-overload-test/src/stdcpp/multimap.rs b/cpp-overload-test/src/stdcpp/multimap.rs new file mode 100644 index 0000000..4d1ee37 --- /dev/null +++ b/cpp-overload-test/src/stdcpp/multimap.rs @@ -0,0 +1,824 @@ +//! Test that C++ overloads for [std::multimap][multimap] can be successfully represented in Rust. +//! +//! [multimap]: https://en.cppreference.com/w/cpp/container/multimap +//! [multimap.rs]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/multimap.rs +//! +//! Overloaded method names wrapped here: +//! - `new`: default, move, slice of pairs, and one, two or three pairs +//! - `insert`: `(key, value)`, `(hint_key, key, value)`, and `(&[(K, V)])` +//! - `erase`: `(key)`, `(first_key, last_key)`, `(&StdMultimapIter)` +//! - `merge`: `(&mut StdMultimap)` +//! +//! # Limitations +//! +//! - The wrapper uses `c_int` for both keys and values because the `overload!` macro +//! does not currently support generics. +//! +//! ## Not wrapped +//! +//! Skipped by task rules: +//! +//! - Overloads that only add an allocator argument. +//! - `operator=` and `swap`, because Rust has no equivalent. +//! - `insert(value_type&&)` and `insert(hint, value_type&&)`: differ from the `const&` +//! versions only by `&&`. +//! +//! Constructors (`new`): +//! - Copy constructor `multimap(const multimap&)`: differs from the wrapped move constructor +//! only by `const&` vs `&&`, so one example is enough. +//! - Compare constructor - `Compare` is a type parameter, and the wrapper is fixed to `std::multimap`. +//! - C++ iterator-range constructor (first, last) - slice constructor covers the idea +//! - std::from_range (C++23) - depends on C++23 library support +//! +//! (`insert`): +//! - `insert(P&&)` - has no Rust equivalent +//! - `insert(node_type&&)` and `insert(hint, node_type&&)` - node handles come from extracting nodes, which is not yet wrapped +//! - `insert(initializer_list)` and `insert(first, last)` - implemented as `insert(&[(K, V)])`(slice overload) +//! +//! (`erase`): +//! - `erase(iterator pos)`: have been covered by `erase(const_iterator pos)` - differs by const +//! - `erase(first, last)` with C++ iterators: approximated by `erase(first_key, last_key)`. +//! - `erase(K&& x)`: the wrapper uses a fixed `int` key type, and the key-based +//! `erase(const key_type& key)` overload already covers the main erasure behaviour. +//! +//! (`merge`): +//! - `merge(std::map<..., C2>&)` and `merge(std::map<..., C2>&&)`: need a `std::map` +//! wrapper type, which does not exist here. +//! - `merge(std::multimap<..., C2>&&)`: differs from the wrapped `merge(std::multimap<..., C2>&)` by `&&`. +//! - Source maps with a comparator other than `std::less`: the macro cannot +//! express the `C2` type parameter. + +#![allow(unused_braces, reason = "Generated by overload! macro")] + +use cpp::cpp; +use splat_overload::overload; +use std::ffi::{c_char, c_size_t, c_void}; +use std::marker::PhantomData; + +cpp! {{ + #include + #include + #include + #include +}} + +/// Workaround for missing generic support in the `overload!` macro. +pub type K = std::ffi::c_int; +pub type V = std::ffi::c_int; + +/// A wrapper struct to hold the returned C++ `std::multimap` pointer. +/// +/// ### Limitations +/// +/// This struct and the impl should be fully generic over the key and value types. +/// FIXME: make the `overload!` macro support generics. +pub struct StdMultimap(*mut c_void, PhantomData<(K, V)>); + +/// A wrapper struct to hold a C++ `std::multimap::const_iterator` object. +/// +/// ### Limitations +/// +/// The iterator is not tied to the map's lifetime. A borrowing iterator would stop +/// `map.erase(&pos)` from compiling, because `erase` needs `&mut map` while the iterator +/// still borrows the map. +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdMultimapIter(*mut c_void, PhantomData<(K, V)>); + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +#[allow( + non_upper_case_globals, + reason = "Triggered by cpp! macro in rust-analyzer" +)] +// The cpp! macro doesn't work inside the overload! macro, so we extract C++ calls into separate methods. +// A mature overload feature (or a production C++ project) wouldn't need this impl block. +impl StdMultimap { + // Create an empty C++ std::multimap. + // not overloaded, but used to implement the default constructor. + fn default() -> StdMultimap { + let map = unsafe { + cpp!([] -> *mut c_void as "std::multimap*" { + return new std::multimap(); + }) + }; + + StdMultimap(map, PhantomData) + } + + // Returns the number of key-value pairs. + // not overloaded, but used to check that the multimap is working correctly. + fn len(&self) -> c_size_t { + let map = self.0 as *const c_void; + + unsafe { + cpp!([map as "const std::multimap*"] + -> c_size_t as "size_t" { + return map->size(); + }) + } + } + + // not overloaded, but used to check that the multimap is working correctly. + fn is_empty(&self) -> bool { + let map = self.0 as *const c_void; + + let empty = unsafe { + cpp!([map as "const std::multimap*"] -> c_char as "char" { + // `char` is FFI-safe, but Rust `bool` is not. + // C++ automatically promotes `bool` to `char`. + return map->empty(); + }) + }; + + empty != 0 + } + + // Construct a multimap from a single key-value pair. + fn from_initializer_list_1(a: (K, V)) -> StdMultimap { + let (ak, av) = a; + let map = unsafe { + cpp!([ak as "int", av as "int"] -> *mut c_void as "std::multimap*" { + return new std::multimap{ {ak, av} }; + }) + }; + StdMultimap(map, PhantomData) + } + + fn from_initializer_list_2(a: (K, V), b: (K, V)) -> StdMultimap { + let (ak, av) = a; + let (bk, bv) = b; + + let map = unsafe { + cpp!([ + ak as "int", av as "int", bk as "int", bv as "int"] -> *mut c_void as "std::multimap*" { + return new std::multimap{{ak, av},{bk, bv} }; + }) + }; + + StdMultimap(map, PhantomData) + } + + fn from_initializer_list_3(a: (K, V), b: (K, V), c: (K, V)) -> StdMultimap { + let (ak, av) = a; + let (bk, bv) = b; + let (ck, cv) = c; + + let map = unsafe { + cpp!([ + ak as "int", av as "int", + bk as "int", bv as "int", + ck as "int", cv as "int" + ] -> *mut c_void as "std::multimap*" { + return new std::multimap{ + {ak, av}, + {bk, bv}, + {ck, cv} + }; + }) + }; + + StdMultimap(map, PhantomData) + } + + // Number of elements with this key (not overloaded, used to check duplicates are kept). + fn count_key(&self, key: K) -> c_size_t { + let slf = self.0 as *const c_void; + unsafe { + cpp!([slf as "const std::multimap*", key as "int"] -> c_size_t as "size_t" { + return slf->count(key); + }) + } + } + + // Not overloaded, but used to get an iterator to pass to `erase(pos)`. + // Returns an iterator to the first element with a key >= `key`, or `end()`. + fn lower_bound(&self, key: K) -> StdMultimapIter { + let map = self.0 as *const c_void; + let iter = unsafe { + // FIXME: is the returned type always a pointer type? + cpp!([map as "const std::multimap*", key as "int"] + -> *mut c_void as "std::multimap::const_iterator" { + return map->lower_bound(key); + }) + }; + StdMultimapIter(iter, PhantomData) + } + + // Construct a new multimap by moving from another multimap. + fn move_from(other: &mut StdMultimap) -> StdMultimap { + let other_map = other.0; + + let map = unsafe { + cpp!([other_map as "std::multimap*"] + -> *mut c_void as "std::multimap*" { + return new std::multimap(std::move(*other_map)); + }) + }; + + StdMultimap(map, PhantomData) + } + + fn from_slice(entries: &[(K, V)]) -> StdMultimap { + // An empty Rust slice has a dangling pointer, see + // + if entries.is_empty() { + return StdMultimap::default(); + } + + // Rust tuples are not assumed to have the same layout as C++ pairs. + // Split the entries into separate arrays for the FFI boundary. + let keys: Vec = entries.iter().map(|(key, _)| *key).collect(); + let values: Vec = entries.iter().map(|(_, value)| *value).collect(); + + let keys_ptr = keys.as_ptr(); + let values_ptr = values.as_ptr(); + let count = entries.len(); + + // SAFETY: both pointers are non-null and point to `count` initialized ints. + // `keys` and `values` stay alive until after the C++ call returns. + let map = unsafe { + cpp!([ + keys_ptr as "const int*", + values_ptr as "const int*", + count as "size_t" + ] -> *mut c_void as "std::multimap*" { + std::vector> pairs; + pairs.reserve(count); // avoids repeated reallocation + + for (size_t i = 0; i < count; ++i) { + pairs.emplace_back(keys_ptr[i], values_ptr[i]); + } + + return new std::multimap( + pairs.begin(), + pairs.end() + ); + }) + }; + + StdMultimap(map, PhantomData) + } + + // Insert one key-value pair (the C++ `insert(value)` overload). + fn insert_internal(&mut self, key: K, value: V) { + let map = self.0; + + // SAFETY: `map` is a valid pointer to a live `std::multimap`, owned by + // `self`, and `&mut self` guarantees no other access during the call. + unsafe { + cpp!([ + map as "std::multimap*", + key as "int", + value as "int" + ] { + map->insert({key, value}); + }) + } + } + + // Insert one key-value pair, using the position found by `lower_bound(hint_key)` + // as the hint (the C++ `insert(hint, value)` overload). + fn insert_with_hint_internal(&mut self, hint_key: K, key: K, value: V) { + let map = self.0; + + // SAFETY: `map` is a valid pointer to a live `std::multimap`, owned by + // `self`, and `&mut self` guarantees no other access during the call. + unsafe { + cpp!([ + map as "std::multimap*", + hint_key as "int", + key as "int", + value as "int" + ] { + auto hint = map->lower_bound(hint_key); + map->insert(hint, {key, value}); + }) + } + } + + // Insert every pair from a slice `insert(first, last)` and + // `insert(initializer_list)` overloads. + fn insert_slice_internal(&mut self, entries: &[(K, V)]) { + // Inserting an empty slice should leave the map unchanged. + if entries.is_empty() { + return; + } + + // Rust tuples are not assumed to have the same layout as C++ pairs. + let keys: Vec = entries.iter().map(|(key, _)| *key).collect(); + let values: Vec = entries.iter().map(|(_, value)| *value).collect(); + + let keys_ptr = keys.as_ptr(); + let values_ptr = values.as_ptr(); + let count = entries.len(); + let map_ptr = self.0; + + // SAFETY: both pointers point to `count` initialized ints, and `keys` and + // `values` stay alive until the C++ call returns. `map_ptr` is a valid pointer + // owned by `self`, and `&mut self` guarantees no other access. + unsafe { + cpp!([ + map_ptr as "std::multimap*", + keys_ptr as "const int*", + values_ptr as "const int*", + count as "size_t" + ] { + for (size_t i = 0; i < count; ++i) { + map_ptr->insert({keys_ptr[i], values_ptr[i]}); + } + }) + } + } + + // Erase every element with this key (the C++ `erase(const key_type&)` overload). + fn erase_key_internal(&mut self, key: K) -> c_size_t { + let map = self.0; + + // SAFETY: `map` is a valid pointer owned by `self`, and `&mut self` + // guarantees no other access during the call. + unsafe { + cpp!([map as "std::multimap*", key as "int"] -> c_size_t as "size_t" { + return map->erase(key); + }) + } + } + + // Erase every element with a key in [first_key, last_key) (the C++ `erase(first, last)` overload). + fn erase_range_internal(&mut self, first_key: K, last_key: K) -> c_size_t { + let map = self.0; + + // SAFETY: `map` is a valid pointer owned by `self`, and `&mut self` + // guarantees no other access during the call. + unsafe { + cpp!([ + map as "std::multimap*", + first_key as "int", + last_key as "int" + ] -> c_size_t as "size_t" { + if (last_key <= first_key) { + return 0; + } + auto first = map->lower_bound(first_key); + auto last = map->lower_bound(last_key); + size_t removed = std::distance(first, last); + map->erase(first, last); + return removed; + }) + } + } + + // Erase the element at `pos` (the C++ `erase(const_iterator)` overload). + fn erase_pos_internal(&mut self, pos: &StdMultimapIter) { + let map = self.0; + let pos = pos.0 as *const c_void; + + // SAFETY: `map` is a valid pointer owned by `self`, and `&mut self` guarantees no + // other access during the call. Erasing `end()` is undefined behaviour in C++, so + // it is checked first. `pos` must come from this map and must not be invalidated. + unsafe { + cpp!([ + map as "std::multimap*", + pos as "std::multimap::const_iterator" + ] { + if (pos != map->cend()) { + map->erase(pos); + } + }) + } + } + + // Move all elements from `other` into this multimap (the C++ `merge(multimap&)` overload). + fn merge_internal(&mut self, other: &mut StdMultimap) { + let map = self.0; + let other_map = other.0; + + // SAFETY: both pointers are valid, live multimaps. `&mut self` and `&mut other` + // cannot alias, so they are distinct maps, as `merge` requires. + unsafe { + cpp!([ + map as "std::multimap*", + other_map as "std::multimap*" + ] { + map->merge(*other_map); + }) + } + } +} + +// We ignore constructors that only differ by an allocator argument, because they're not interesting overloads to test. +overload! { + impl StdMultimap { + /// Construct an empty multimap. + pub fn new() -> StdMultimap { + StdMultimap::default() + } + + /// Move-construct - a multimap from a mutable multimap reference. + /// + /// The contents of `other` are moved into the new map, leaving `other` in a valid + /// but unspecified state. + /// + /// ### Limitations + /// + /// C++ also has a copy constructor, `multimap(const multimap&)`, which differs only + /// by `const&` vs `&&`, so only the move form is wrapped. + /// + /// ### Ergonomics + /// + /// Rust moves by value, so taking `&mut` and leaving the source usable is unusual + /// and may surprise callers. + pub fn new(other: &mut StdMultimap) -> StdMultimap { + StdMultimap::move_from(other) + } + + /// Construct a multimap from a slice of key-value pairs. + /// + /// ### Limitations + /// + /// Rust tuples are not FFI-safe, so the pairs are split into separate key and value + /// arrays and rebuilt as C++ pairs. This costs extra copies. It also mirrors the C++ + /// iterator-range constructor, but takes a Rust slice rather than C++ iterators. + pub fn new(entries: &[(K, V)]) -> StdMultimap { + StdMultimap::from_slice(entries) + } + + /// Construct a multimap from the supplied key-value pairs. + /// + /// Each argument represents one `(key, value)` pair. The resulting + /// multimap contains all supplied pairs, including pairs with duplicate keys. + /// + /// ### Limitations + /// + /// This is just an example; a production implementation would support + /// any number of key-value pairs. + /// + /// Rust doesn't have a language equivalent to C++'s `std::initializer_list`, + /// and it doesn't have variadic tuples, so we provide separate constructors for + /// one, two, and three key-value pairs instead of a single constructor that + /// accepts any number of pairs. + /// + /// These overloads do not clash with the other `new` overloads. Each pair is a + /// tuple `(K, V)`, which is a different type from `&StdMultimap` and `&[(K, V)]`. + /// + /// These examples use fixed-size tuples for key-value pairs because the + /// current wrapper uses `c_int` for both keys and values. The underlying + /// C++ type and FFI conversions are defined for `std::multimap`. + /// + /// ### Ergonomics + /// + /// Each key-value pair must be passed as a tuple. For example, constructing a multimap with + /// one pair requires `StdMultimap::new((1, 10))`, while multiple pairs can be passed as + /// `StdMultimap::new((1, 10), (2, 20))`. + /// The double parentheses can make the syntax confusing, especially + /// for users who might expect to pass the key and value as separate + /// arguments, such as `StdMultimap::new(1, 10)`. + pub fn new(a: (K, V)) -> StdMultimap { + StdMultimap::from_initializer_list_1(a) + } + + pub fn new(a: (K, V), b: (K, V)) -> StdMultimap { + StdMultimap::from_initializer_list_2(a, b) + } + + pub fn new(a: (K, V), b: (K, V), c: (K, V)) -> StdMultimap { + StdMultimap::from_initializer_list_3(a, b, c) + } + } + +} + +overload! { + impl StdMultimap { + /// Insert a key-value pair into the multimap. + /// Duplicate keys are kept. + /// + /// ### Limitations + /// + /// C++ `insert(value)` returns an iterator to the inserted element. This wrapper + /// returns nothing, because the wrapper does not return iterators. Callers can use + /// `count_key` to check the result instead. + /// + /// C++ also takes a single `std::pair`, but Rust tuples are not + /// FFI-safe, so this overload takes the key and value as two separate arguments. + /// + /// ### Ergonomics + /// + /// `insert(1, 10)` and the hint overload `insert(0, 1, 10)` differ only in + /// argument count, so it is easy to mix up which number is the key, the value, or + /// the hint. + pub fn insert(&mut self, key: K, value: V) { + self.insert_internal(key, value); + } + + /// Insert a key-value pair, using the position of `hint_key` as a placement hint. + /// + /// ### Limitations + /// + /// C++ takes an iterator as the hint. This wrapper takes a key and calls + /// `lower_bound` to produce the iterator, so callers cannot pass an arbitrary + /// iterator. The return iterator is also dropped, as above. + /// + /// ### Ergonomics + /// + /// The hint is only a performance hint. A wrong `hint_key` never changes which elements + /// are stored, but can change order for equal keys. + pub fn insert(&mut self, hint_key: K, key: K, value: V) { + self.insert_with_hint_internal(hint_key, key, value) + } + + /// Insert every key-value pair from a slice. Duplicate keys are kept. + /// + /// ### Limitations + /// + /// This one overload stands in for two C++ overloads: `insert(first, last)` and + /// `insert(initializer_list)`. Rust has no `initializer_list`, and a slice covers the + /// same use. It takes a Rust slice, not C++ iterators, so a range from another C++ + /// container can't be passed in. + /// + /// Rust tuples are not FFI-safe, so the pairs are split into two arrays first, which + /// costs extra copies. + /// + /// ### Ergonomics + /// + /// An array needs a slice conversion, or a typed variable first, for example + /// `let entries: &[(K, V)] = &[(1, 10)];`. + pub fn insert(&mut self, entries: &[(K, V)]) { + self.insert_slice_internal(entries) + } + } +} + +overload! { + impl StdMultimap { + /// Erase every element whose key equals `key`. + /// Returns the number of elements removed. + /// + /// Unlike a normal map, a multimap can hold several elements per key, so + /// removes all of them, not just one. + /// + /// ### Ergonomics + /// + /// `erase(key)` and `erase(first_key, last_key)` differ only in argument count, + /// so it is easy to confuse a single key with the start of a range. + pub fn erase(&mut self, key: K) -> c_size_t { + self.erase_key_internal(key) + } + + /// Erase every element with a key in `[first_key, last_key)`. + /// Returns the number of elements removed. + /// + /// ### Limitations + /// + /// C++ takes two iterators and returns an iterator to the element after the + /// erased range. This wrapper takes two keys, finds the iterators with + /// `lower_bound`, and returns the number of elements removed instead. + pub fn erase(&mut self, first_key: K, last_key: K) -> c_size_t { + self.erase_range_internal(first_key, last_key) + } + + /// Erase the element at `pos`. + /// + /// Unlike `erase(key)`, this removes exactly one element, even if the key has + /// duplicates. + /// + /// ### Limitations + /// + /// C++ also has `erase(iterator)`, which differs only by iterator constness, so + /// only `erase(const_iterator)` is wrapped. + /// + /// The iterator is not tied to the map's lifetime, so using `pos` after the map + /// is modified is undefined behaviour. + /// + /// ### Ergonomics + /// + /// This does not clash with `erase(key: K)`, because `&StdMultimapIter` is a + /// different type from `K`. + pub fn erase(&mut self, pos: &StdMultimapIter) { + self.erase_pos_internal(pos) + } + } +} + +overload! { + impl StdMultimap { + /// Move all elements from `other` into this multimap. + /// + /// Elements are moved, not copied. A multimap accepts duplicate keys, so every + /// element moves and `other` is empty afterwards. + /// + /// ### Limitations + /// + /// C++ is a template over the source's comparator type `C2`. The wrapper is fixed + /// to `std::multimap`, so only the same comparator is supported. + /// + /// ### Ergonomics + /// + /// Merging a map into itself is impossible, because `&mut self` and `&mut other` + /// cannot alias. + pub fn merge(&mut self, other: &mut StdMultimap) { + self.merge_internal(other) + } + } +} + +// The C++ multimap is deleted when the Rust wrapper is dropped. +impl Drop for StdMultimap { + fn drop(&mut self) { + let map = self.0; + // SAFETY: `map` was created by `new` in this wrapper, is deleted only here, and + // `self` is being dropped, so it cannot be used afterwards + + unsafe { + cpp!([map as "std::multimap*"] { + delete map; + }) + } + } +} + +// These tests show that the correct Rust and C++ overloads are being called. +#[test] +fn test_multimap() { + let default_map = StdMultimap::new(); + assert!(default_map.is_empty()); + assert_eq!(default_map.len(), 0); + + let one = StdMultimap::new((1, 10)); + assert_eq!(one.len(), 1); + assert_eq!(one.count_key(1), 1); +} + +#[test] +fn test_multimap_initializer_list_two_pairs() { + let map = StdMultimap::new((2, 20), (1, 10)); + + assert_eq!(map.len(), 2); + assert_eq!(map.count_key(1), 1); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_initializer_list_three_pairs() { + let map = StdMultimap::new((1, 10), (1, 20), (2, 30)); + + assert_eq!(map.len(), 3); + assert_eq!(map.count_key(1), 2); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_move_constructor() { + let mut original = StdMultimap::new((1, 10), (1, 20)); + + let moved = StdMultimap::new(&mut original); + + assert_eq!(moved.len(), 2); + assert_eq!(moved.count_key(1), 2); + + let _ = original.len(); + original.insert(5, 50); + assert_eq!(original.count_key(5), 1); +} + +#[test] +fn test_multimap_empty_slice_constructor() { + let entries: &[(K, V)] = &[]; + let map = StdMultimap::new(entries); + + assert!(map.is_empty()); + assert_eq!(map.len(), 0); +} + +#[test] +fn test_multimap_slice_constructor_with_duplicate_keys() { + let entries: &[(K, V)] = &[(1, 10), (2, 20), (1, 30)]; + + let map = StdMultimap::new(entries); + + assert_eq!(map.len(), 3); + assert_eq!(map.count_key(1), 2); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_insert() { + let mut map = StdMultimap::new(); + + assert!(map.is_empty()); + + map.insert(1, 10); + map.insert(1, 20); + map.insert(2, 50); + + assert_eq!(map.len(), 3); + assert_eq!(map.count_key(1), 2); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_insert_hint_overload() { + let mut map = StdMultimap::new(); + + assert!(map.is_empty()); + + map.insert(0, 1, 10); + map.insert(1, 1, 20); + map.insert(2, 2, 50); + + assert_eq!(map.len(), 3); + assert_eq!(map.count_key(1), 2); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_insert_slice() { + let mut map = StdMultimap::new(); + map.insert(1, 10); + + let entries: &[(K, V)] = &[(2, 20), (3, 30), (2, 25)]; + map.insert(entries); + + assert_eq!(map.len(), 4); + assert_eq!(map.count_key(1), 1); + assert_eq!(map.count_key(2), 2); + assert_eq!(map.count_key(3), 1); +} + +#[test] +fn test_multimap_insert_empty_slice() { + let mut map = StdMultimap::new(); + map.insert(1, 10); + + let entries: &[(K, V)] = &[]; + map.insert(entries); + + assert_eq!(map.len(), 1); + assert_eq!(map.count_key(1), 1); +} + +#[test] +fn test_multimap_erase_key() { + let mut map = StdMultimap::new((1, 10), (1, 20), (2, 30)); + + assert_eq!(map.erase(1), 2); + assert_eq!(map.len(), 1); + assert_eq!(map.count_key(1), 0); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_erase_key_range() { + let entries: &[(K, V)] = &[(1, 10), (1, 11), (2, 20), (3, 30), (4, 40)]; + let mut map = StdMultimap::new(entries); + + // Removes keys 1 and 2, keeps 3 and 4 (last key is excluded). + assert_eq!(map.erase(1, 3), 3); + assert_eq!(map.len(), 2); + assert_eq!(map.count_key(3), 1); + assert_eq!(map.count_key(4), 1); +} + +#[test] +fn test_multimap_erase_empty_or_reversed_range() { + let mut map = StdMultimap::new((1, 10), (2, 20)); + + assert_eq!(map.erase(2, 2), 0); + assert_eq!(map.erase(3, 1), 0); + assert_eq!(map.len(), 2); +} + +#[test] +fn test_multimap_erase_iterator() { + let mut map = StdMultimap::new((1, 10), (1, 20), (2, 30)); + + let pos = map.lower_bound(1); + map.erase(&pos); + + // Unlike `erase(key)`, this removes only one of the duplicates. + assert_eq!(map.len(), 2); + assert_eq!(map.count_key(1), 1); + assert_eq!(map.count_key(2), 1); +} + +#[test] +fn test_multimap_erase_end_iterator() { + let mut map = StdMultimap::new((1, 10), (2, 20)); + + let end = map.lower_bound(99); // past every key, so this is `end()` + map.erase(&end); + + assert_eq!(map.len(), 2); +} + +#[test] +fn test_multimap_merge() { + let mut a = StdMultimap::new((1, 10), (2, 20)); + let mut b = StdMultimap::new((1, 11), (3, 30)); + + a.merge(&mut b); + + assert_eq!(a.len(), 4); + assert_eq!(a.count_key(1), 2); + assert_eq!(a.count_key(2), 1); + assert_eq!(a.count_key(3), 1); + assert!(b.is_empty()); +} diff --git a/cpp-overloads-in-rust.md b/cpp-overloads-in-rust.md index b9b321b..f8a355a 100644 --- a/cpp-overloads-in-rust.md +++ b/cpp-overloads-in-rust.md @@ -28,6 +28,7 @@ See the Limitations section below the table for more details. | *Template Classes* | Constructors | Accessors | Modifiers | Transforms | | [forward_list] | ✅ | 🌗⚙️[^1] | 🌗⚙️?[^2] | ✅ | | [hash] | ✅ | ✅ | | | +| [multimap] | ✅ | 🌗⚙️?[^ma] | 🌗?[^mm] | | | [unordered_map] | ✅ | 🌗⚙️[^5] | 🌗⚙️ | | | [unordered_multiset] | ✅ | ✅⚙️[^3] | ✅ | ?[^4] | | TODO | ? | ? | ? | ? | @@ -39,11 +40,14 @@ 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 +[multimap]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/multimap.rs [unordered_map]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/unordered_map.rs [unordered_multiset]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/unordered_multiset.rs [^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. +[^ma]: `lower_bound`, `upper_bound`, `equal_range` and `at` still need to be analysed. +[^mm]: `erase(key)` and `erase(pos)` conflict unless the iterator is a distinct type. `emplace` and `extract` 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]: `find` and `max_load_factor` use associated functions instead of methods due to macro limitations with `&self` vs `&mut self`.