diff --git a/cpp-overload-test/examples/stdcpp-unordered-map.rs b/cpp-overload-test/examples/stdcpp-unordered-map.rs new file mode 100644 index 0000000..612bb94 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-unordered-map.rs @@ -0,0 +1,86 @@ +//! Test C++ [std::unordered_map][unordered_map] overloads that can't be represented in Rust, and +//! record the macro diagnostics. Error output is in `stdcpp-unordered-map.stderr`. +//! Working overloads are in `src/stdcpp/unordered_map.rs`. +//! +//! [unordered_map]: https://en.cppreference.com/cpp/container/unordered_map +#![feature(splat, tuple_trait)] +#![allow(incomplete_features)] +#![allow(unused_braces)] + +use splat_overload::overload; + +/// Workaround for missing generic support in the `overload!` macro: the map key type. +type K = std::ffi::c_int; + +/// Workaround for missing generic support in the `overload!` macro: the mapped value type. +type V = std::ffi::c_int; + +/// A fake key-value pair, matching the working `KeyValue` type. +#[expect(dead_code, reason = "Incompatible overloads can't be used")] +#[derive(Copy, Clone)] +struct KeyValue { + key: K, + value: V, +} + +/// A wrapper struct to hold the returned C++ pointer. +/// We fake the C++ map pointer with a Rust vector to help with type checking. +#[expect(dead_code, reason = "Incompatible overloads can't be used")] +struct StdUnorderedMap(Vec); + +// Accessors: `max_load_factor(...)` +overload! { + impl StdUnorderedMap { + /// Gets the maximum load factor, a `const` method in C++. + /// + /// ### Incompatibilities + /// + /// The `overload!` macro requires all overloads in a group to have matching + /// receiver kinds, but `max_load_factor` uses both `&self` and `&mut self`. + /// + /// ### Workarounds + /// + /// Use associated functions with explicit `&StdUnorderedMap` and + /// `&mut StdUnorderedMap` parameters, as in `src/stdcpp/unordered_map.rs`. + fn max_load_factor(&self) -> f32 { + todo!() + } + + /// Set the maximum load factor, a non-`const` method in C++. + fn max_load_factor(&mut self, _ml: f32) { + todo!() + } + } +} + +// Accessors: `contains(...)` +overload! { + impl StdUnorderedMap { + /// Check whether the map contains `key`. + fn contains(&self, _key: &K) -> bool { + todo!() + } + + /// Checks whether the map contains a key equal to `key`, even if it has a + /// different type from the map's key type. + /// + /// ### Incompatibilities + /// + /// The `overload!` macro does not preserve generic parameters, leaving `Q` + /// unresolved in the generated code. + /// + /// ### Limitations + /// + /// C++ only allows this overload when both `Hash` and `KeyEqual` support + /// transparent lookup through `is_transparent`. Rust has no direct equivalent. + /// + /// ### Workaround + /// + /// Use `HashMap::get` with the `K: Borrow` trait bound instead. + fn contains(&self, _key: &Q) -> bool { + todo!() + } + } +} + +pub fn main() {} diff --git a/cpp-overload-test/examples/stdcpp-unordered-map.stderr b/cpp-overload-test/examples/stdcpp-unordered-map.stderr new file mode 100644 index 0000000..4784611 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-unordered-map.stderr @@ -0,0 +1,32 @@ +error: proc macro panicked + --> cpp-overload-test/examples/stdcpp-unordered-map.rs:32:1 + | +32 | / overload! { +33 | | impl StdUnorderedMap { +34 | | /// Gets the maximum load factor, a `const` method in C++. +35 | | /// +... | +54 | | } + | |_^ + | + = help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got max_load_factor: &self and max_load_factor: &mut self + +error[E0425]: cannot find type `Q` in this scope + --> cpp-overload-test/examples/stdcpp-unordered-map.rs:80:38 + | +80 | fn contains(&self, _key: &Q) -> bool { + | ^ not found in this scope + | +note: similarly named type alias `K` defined here + --> cpp-overload-test/examples/stdcpp-unordered-map.rs:13:1 + | +13 | type K = std::ffi::c_int; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: a type alias with a similar name exists + | +80 - fn contains(&self, _key: &Q) -> bool { +80 + fn contains(&self, _key: &K) -> bool { + | + +For more information about this error, try `rustc --explain E0425`. +error: could not compile `cpp-overload-test` (example "stdcpp-unordered-map") due to 2 previous errors diff --git a/cpp-overload-test/src/lib.rs b/cpp-overload-test/src/lib.rs index 88ec235..f6b1a92 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 unordered_map; pub mod unordered_multiset; } diff --git a/cpp-overload-test/src/stdcpp/unordered_map.rs b/cpp-overload-test/src/stdcpp/unordered_map.rs new file mode 100644 index 0000000..0e2f37c --- /dev/null +++ b/cpp-overload-test/src/stdcpp/unordered_map.rs @@ -0,0 +1,793 @@ +//! Test that C++ overloads for [std::unordered_map][unordered_map] can be successfully represented in Rust. +//! [unordered_map]: https://en.cppreference.com/cpp/container/unordered_map +#![allow(unused_braces, reason = "Generated by overload! macro")] + +use cpp::cpp; +use splat_overload::overload; +use std::ffi::{c_char, c_float, c_size_t, c_void}; +use std::marker::PhantomData; + +// C++ header includes +cpp! {{ + #include + #include + #include + #include + #include + #include + + typedef std::unordered_map UnorderedMapCpp; + + struct KeyValueCpp { + int key; + int value; + }; +}} + +/// Workaround for missing generic support in the `overload!` macro. +pub type K = std::ffi::c_int; + +/// Same as `K`. +pub type V = std::ffi::c_int; + +/// One key-value pair crossing the FFI boundary. `std::pair` and Rust tuples have no guaranteed +/// matching layout, so pairs get copied through this struct. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct KeyValue { + pub key: K, + pub value: V, +} + +/// Holds a pointer to a C++ `std::unordered_map`. +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdUnorderedMap(*mut c_void, PhantomData); + +/// `Access` is `*const V` for a `const_iterator` and `*mut V` for an `iterator`. +/// +/// ### Limitations +/// +/// The iterator doesn't borrow the map. `insert` with a hint and `erase` need a `&mut` map plus an +/// iterator into it, which the borrow checker wouldn't allow otherwise. +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdUnorderedMapIter(*mut c_void, PhantomData); + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +impl StdUnorderedMapIter<*const V> { + /// ### Safety + /// + /// The iterator must point at a live element; `end` or an invalidated iterator is undefined behaviour. + unsafe fn entry(&self) -> KeyValue { + let iter = self.0 as *const c_void; + unsafe { + cpp!([iter as "UnorderedMapCpp::const_iterator"] -> KeyValue as "KeyValueCpp" { + return KeyValueCpp { iter->first, iter->second }; + }) + } + } +} + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +impl StdUnorderedMapIter<*mut V> { + /// ### Limitations + /// + /// This returns a copy of the inner value, but a non-const iterator can be used to change + /// those values in C++. So returning a KeyValue-like type with &mut would be unsound because + /// multiple iterators could point to the same element. To allow mutation, we would need to use + /// mutable pointers here. + unsafe fn entry(&self) -> KeyValue { + let iter: *mut c_void = self.0; + unsafe { + cpp!([iter as "UnorderedMapCpp::iterator"] -> KeyValue as "KeyValueCpp" { + return KeyValueCpp { iter->first, iter->second }; + }) + } + } + + fn downgrade(self) -> StdUnorderedMapIter<*const V> { + let iter: *mut c_void = self.0; + let iter = unsafe { + cpp!([iter as "UnorderedMapCpp::iterator"] -> *mut c_void as "UnorderedMapCpp::const_iterator" { + return iter; + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } +} + +#[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" +)] +impl StdUnorderedMap { + // The C++ implementations for the `new(...)` overloads. + fn default() -> StdUnorderedMap { + let map = unsafe { + cpp!([] -> *mut c_void as "UnorderedMapCpp*" { + return new UnorderedMapCpp(); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn with_bucket_count(bucket_count: c_size_t) -> StdUnorderedMap { + let map = unsafe { + cpp!([bucket_count as "size_t"] -> *mut c_void as "UnorderedMapCpp*" { + // Use the default values for `hash` and `equal`. + return new UnorderedMapCpp(bucket_count); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn from_slice(items: &[KeyValue]) -> StdUnorderedMap { + // An empty slice has a dangling pointer, so handle it before passing the pointer to C++. + // See this link for reference:. + if items.is_empty() { + return StdUnorderedMap::default(); + } + let len = items.len(); + let items: *const KeyValue = items.as_ptr(); + let map = unsafe { + cpp!([items as "const KeyValueCpp*", len as "size_t"] -> *mut c_void as "UnorderedMapCpp*" { + std::vector> pairs; + pairs.reserve(len); + for (size_t i = 0; i < len; ++i) { + pairs.emplace_back(items[i].key, items[i].value); + } + + return new UnorderedMapCpp(pairs.begin(), pairs.end()); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn from_cpp_iter( + first: &StdUnorderedMapIter<*const V>, + last: &StdUnorderedMapIter<*const V>, + ) -> StdUnorderedMap { + let first = first.0 as *const c_void; + let last = last.0 as *const c_void; + let map = unsafe { + cpp!([first as "UnorderedMapCpp::const_iterator", last as "UnorderedMapCpp::const_iterator"] -> *mut c_void as "UnorderedMapCpp*" { + return new UnorderedMapCpp(first, last); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + /// ### Limitations + /// + /// It has to first Collect into a Rust `Vec`, then copies into a C++ vector. + /// + /// `std::from_range` needs a recent standard library. But on older ones like GCC 13,this falls back to the iterator-range constructor. + fn from_iter(iter: &mut dyn Iterator) -> StdUnorderedMap { + let items: Vec = iter.collect(); + // See this:- . + if items.is_empty() { + return StdUnorderedMap::default(); + } + let len = items.len(); + let items: *const KeyValue = items.as_ptr(); + + let map = unsafe { + cpp!([items as "const KeyValueCpp*", len as "size_t"] -> *mut c_void as "UnorderedMapCpp*" { + std::vector> pairs; + pairs.reserve(len); + for (size_t i = 0; i < len; ++i) { + pairs.emplace_back(items[i].key, items[i].value); + } + #ifdef __cpp_lib_containers_ranges + return new UnorderedMapCpp(std::from_range, pairs); + #else + return new UnorderedMapCpp(pairs.begin(), pairs.end()); + #endif + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn copy_from(other: &StdUnorderedMap) -> StdUnorderedMap { + let other = other.0 as *const c_void; + let map = unsafe { + cpp!([other as "const UnorderedMapCpp*"] -> *mut c_void as "UnorderedMapCpp*" { + return new UnorderedMapCpp(*other); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn move_from(other: &mut StdUnorderedMap) -> StdUnorderedMap { + let other: *mut c_void = other.0; + let map = unsafe { + cpp!([other as "UnorderedMapCpp*"] -> *mut c_void as "UnorderedMapCpp*" { + return new UnorderedMapCpp(std::move(*other)); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn from_initializer_list_1(a: KeyValue) -> StdUnorderedMap { + let map = unsafe { + cpp!([a as "KeyValueCpp"] -> *mut c_void as "UnorderedMapCpp*" { + return new UnorderedMapCpp(std::initializer_list{ + { a.key, a.value }, + }); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + fn from_initializer_list_2(a: KeyValue, b: KeyValue) -> StdUnorderedMap { + let map = unsafe { + cpp!([a as "KeyValueCpp", b as "KeyValueCpp"] -> *mut c_void as "UnorderedMapCpp*" { + return new UnorderedMapCpp(std::initializer_list{ + { a.key, a.value }, + { b.key, b.value }, + }); + }) + }; + StdUnorderedMap(map, PhantomData) + } + + // The C++ implementations for the `find(...)` and `max_load_factor(...)` overloads. + fn find_const(&self, key: K) -> StdUnorderedMapIter<*const V> { + let slf = self.0 as *const c_void; + let iter = unsafe { + cpp!([slf as "const UnorderedMapCpp*", key as "int"] -> *mut c_void as "UnorderedMapCpp::const_iterator" { + return slf->find(key); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn find_mut(&mut self, key: K) -> StdUnorderedMapIter<*mut V> { + let slf: *mut c_void = self.0; + let iter = unsafe { + cpp!([slf as "UnorderedMapCpp*", key as "int"] -> *mut c_void as "UnorderedMapCpp::iterator" { + return slf->find(key); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn max_load_factor_get(&self) -> c_float { + let slf = self.0 as *const c_void; + unsafe { + cpp!([slf as "const UnorderedMapCpp*"] -> c_float as "float" { + return slf->max_load_factor(); + }) + } + } + + fn max_load_factor_set(&mut self, ml: c_float) { + let slf: *mut c_void = self.0; + unsafe { + cpp!([slf as "UnorderedMapCpp*", ml as "float"] { + slf->max_load_factor(ml); + }) + } + } + + // C++ implementations overloads for `insert(...)` and `erase(...)`. + fn insert_value(&mut self, value: KeyValue) -> (StdUnorderedMapIter<*mut V>, bool) { + let slf: *mut c_void = self.0; + let mut iter: *mut c_void = std::ptr::null_mut(); + let inserted = unsafe { + cpp!([slf as "UnorderedMapCpp*", value as "KeyValueCpp", mut iter as "UnorderedMapCpp::iterator"] -> c_char as "char" { + const UnorderedMapCpp::value_type value_pair(value.key, value.value); + // Return both separately coz insert` returns these two values (iterator, bool) + auto result = slf->insert(value_pair); + iter = result.first; + return result.second; + }) + }; + (StdUnorderedMapIter(iter, PhantomData), inserted != 0) + } + + fn insert_hint( + &mut self, + hint: &StdUnorderedMapIter<*const V>, + value: KeyValue, + ) -> StdUnorderedMapIter<*mut V> { + let slf: *mut c_void = self.0; + let hint = hint.0 as *const c_void; + let iter = unsafe { + cpp!([slf as "UnorderedMapCpp*", hint as "UnorderedMapCpp::const_iterator", value as "KeyValueCpp"] -> *mut c_void as "UnorderedMapCpp::iterator" { + const UnorderedMapCpp::value_type value_pair(value.key, value.value); + return slf->insert(hint, value_pair); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn insert_slice(&mut self, items: &[KeyValue]) { + if items.is_empty() { + return; + } + let slf: *mut c_void = self.0; + let len = items.len(); + let items: *const KeyValue = items.as_ptr(); + unsafe { + cpp!([slf as "UnorderedMapCpp*", items as "const KeyValueCpp*", len as "size_t"] { + std::vector> pairs; + pairs.reserve(len); + for (size_t i = 0; i < len; ++i) { + pairs.emplace_back(items[i].key, items[i].value); + } + + slf->insert(pairs.begin(), pairs.end()); + }) + } + } + + fn insert_cpp_iter( + &mut self, + first: &StdUnorderedMapIter<*const V>, + last: &StdUnorderedMapIter<*const V>, + ) { + let slf: *mut c_void = self.0; + let first = first.0 as *const c_void; + let last = last.0 as *const c_void; + unsafe { + cpp!([slf as "UnorderedMapCpp*", first as "UnorderedMapCpp::const_iterator", last as "UnorderedMapCpp::const_iterator"] { + slf->insert(first, last); + }) + } + } + + fn insert_initializer_list_2(&mut self, a: KeyValue, b: KeyValue) { + let slf: *mut c_void = self.0; + unsafe { + cpp!([slf as "UnorderedMapCpp*", a as "KeyValueCpp", b as "KeyValueCpp"] { + slf->insert(std::initializer_list{ + { a.key, a.value }, + { b.key, b.value }, + }); + }) + } + } + + fn erase_iter(&mut self, pos: StdUnorderedMapIter<*mut V>) -> StdUnorderedMapIter<*mut V> { + let slf: *mut c_void = self.0; + let pos: *mut c_void = pos.0; + let iter = unsafe { + cpp!([slf as "UnorderedMapCpp*", pos as "UnorderedMapCpp::iterator"] -> *mut c_void as "UnorderedMapCpp::iterator" { + return slf->erase(pos); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn erase_const_iter( + &mut self, + pos: StdUnorderedMapIter<*const V>, + ) -> StdUnorderedMapIter<*mut V> { + let slf: *mut c_void = self.0; + let pos = pos.0 as *const c_void; + let iter = unsafe { + cpp!([slf as "UnorderedMapCpp*", pos as "UnorderedMapCpp::const_iterator"] -> *mut c_void as "UnorderedMapCpp::iterator" { + return slf->erase(pos); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn erase_range( + &mut self, + first: StdUnorderedMapIter<*const V>, + last: StdUnorderedMapIter<*const V>, + ) -> StdUnorderedMapIter<*mut V> { + let slf: *mut c_void = self.0; + let first = first.0 as *const c_void; + let last = last.0 as *const c_void; + let iter = unsafe { + cpp!([slf as "UnorderedMapCpp*", first as "UnorderedMapCpp::const_iterator", last as "UnorderedMapCpp::const_iterator"] -> *mut c_void as "UnorderedMapCpp::iterator" { + return slf->erase(first, last); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn erase_key(&mut self, key: K) -> c_size_t { + let slf: *mut c_void = self.0; + unsafe { + cpp!([slf as "UnorderedMapCpp*", key as "int"] -> c_size_t as "size_t" { + return slf->erase(key); + }) + } + } + + fn len(&self) -> c_size_t { + let slf = self.0 as *const c_void; + unsafe { + cpp!([slf as "const UnorderedMapCpp*"] -> c_size_t as "size_t" { + return slf->size(); + }) + } + } + + fn bucket_count(&self) -> c_size_t { + let slf = self.0 as *const c_void; + unsafe { + cpp!([slf as "const UnorderedMapCpp*"] -> c_size_t as "size_t" { + return slf->bucket_count(); + }) + } + } + + fn get(&self, key: K) -> Option<&V> { + let slf = self.0 as *const c_void; + unsafe { + cpp!([slf as "const UnorderedMapCpp*", key as "int"] -> Option<&V> as "const int*" { + auto iter = slf->find(key); + if (iter == slf->end()) { + return nullptr; + } else { + return &iter->second; + } + }) + } + } + + fn begin_const(&self) -> StdUnorderedMapIter<*const V> { + let slf = self.0 as *const c_void; + let iter = unsafe { + cpp!([slf as "const UnorderedMapCpp*"] -> *mut c_void as "UnorderedMapCpp::const_iterator" { + return slf->cbegin(); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn end_const(&self) -> StdUnorderedMapIter<*const V> { + let slf = self.0 as *const c_void; + let iter = unsafe { + cpp!([slf as "const UnorderedMapCpp*"] -> *mut c_void as "UnorderedMapCpp::const_iterator" { + return slf->cend(); + }) + }; + StdUnorderedMapIter(iter, PhantomData) + } + + fn is_end_const(&self, iter: &StdUnorderedMapIter<*const V>) -> bool { + let slf = self.0 as *const c_void; + let iter = iter.0 as *const c_void; + let is_end = unsafe { + cpp!([slf as "const UnorderedMapCpp*", iter as "UnorderedMapCpp::const_iterator"] -> c_char as "char" { + return iter == slf->cend(); + }) + }; + is_end != 0 + } + + fn is_end_mut(&self, iter: &StdUnorderedMapIter<*mut V>) -> bool { + let slf = self.0 as *const c_void; + let iter: *mut c_void = iter.0; + let is_end = unsafe { + cpp!([slf as "const UnorderedMapCpp*", iter as "UnorderedMapCpp::iterator"] -> c_char as "char" { + return UnorderedMapCpp::const_iterator(iter) == slf->cend(); + }) + }; + is_end != 0 + } +} + +// `new(...)` Overloads +// These overloads only differ by an allocator argument, which looks like it is not worth testing here... +overload! { + impl StdUnorderedMap { + pub fn new() -> StdUnorderedMap { + StdUnorderedMap::default() + } + + /// Create an empty map with room for at least `bucket_count` buckets. + /// + /// ### Limitations + /// + /// C++ also takes `hash` and `equal` with defaults. Rust has no default arguments, so a separate overload would be needed for each combination. + // This wrapper keeps the defaults. + pub fn new(bucket_count: c_size_t) -> StdUnorderedMap { + StdUnorderedMap::with_bucket_count(bucket_count) + } + + /// Creating a map by copying key-value pairs from slice. + pub fn new(items: &[KeyValue]) -> StdUnorderedMap { + StdUnorderedMap::from_slice(items) + } + + /// Create a map from the C++ range ie `[first, last)`. The `last` element is excluded. + pub fn new(first: &StdUnorderedMapIter<*const V>, last: &StdUnorderedMapIter<*const V>) -> StdUnorderedMap { + StdUnorderedMap::from_cpp_iter(first, last) + } + + /// Creating a map from an iterator. + /// + /// Callers cast to `&mut dyn Iterator`, same as for `StdForwardList`. + /// FIXME: make the macro support generic iterators, or handle `impl Iterator` correctly. + pub fn new(iter: &mut dyn Iterator) -> StdUnorderedMap { + StdUnorderedMap::from_iter(iter) + } + + /// Copying pairs into new map. + pub fn new(other: &StdUnorderedMap) -> StdUnorderedMap { + StdUnorderedMap::copy_from(other) + } + + /// Move the pairs into new map. + pub fn new(other: &mut StdUnorderedMap) -> StdUnorderedMap { + StdUnorderedMap::move_from(other) + } + + /// Construct a map from the supplied pairs. + + pub fn new(a: KeyValue) -> StdUnorderedMap { + StdUnorderedMap::from_initializer_list_1(a) + } + + pub fn new(a: KeyValue, b: KeyValue) -> StdUnorderedMap { + StdUnorderedMap::from_initializer_list_2(a, b) + } + } +} + +// `find(...)` Accessors +overload! { + impl StdUnorderedMap { + /// Find `key` and return a constant iterator. If it is not found, return an `end` iterator. + /// + /// ### Limitations: + /// `overload!` can't tell `&self` from `&mut self`, so these are associated functions, + /// like `StdForwardList::front`. + + pub fn find(this: &StdUnorderedMap, key: K) -> StdUnorderedMapIter<*const V> { + StdUnorderedMap::find_const(this, key) + } + + /// Find `key` and return a mutable iterator. If it is not found, return an `end` iterator. + pub fn find(this: &mut StdUnorderedMap, key: K) -> StdUnorderedMapIter<*mut V> { + StdUnorderedMap::find_mut(this, key) + } + } +} + +// Accessors: `max_load_factor(...)` +overload! { + impl StdUnorderedMap { + /// Get the maximum average number of elements in each bucket. + /// + /// ### Limitations + /// + /// In C++ the getter is `const` and the setter isn't. `overload!` can't overload on + /// different receivers, so `&` and `&mut` are passed explicitly. + pub fn max_load_factor(this: &StdUnorderedMap) -> c_float { + StdUnorderedMap::max_load_factor_get(this) + } + + pub fn max_load_factor(this: &mut StdUnorderedMap, ml: c_float) { + StdUnorderedMap::max_load_factor_set(this, ml) + } + } +} + +// Modifiers: `insert(...)` +overload! { + impl StdUnorderedMap { + /// Insert `value` if its key is not already in the map. Return an iterator to that key + /// and `true` if the value was inserted. + /// + /// ### Ergonomics + /// + /// Unlike Rust's `HashMap::insert`, this won't overwrite an existing value; C++'s + /// `insert_or_assign` does that instead. + /// + /// ### Limitations + /// + /// C++ has three overloads here: `const value_type&`, `value_type&&`, and a generic + /// `P&&` that converts to `value_type`. `KeyValue` covers the first two, but keeping + /// both clashes with the overloads in the failing example. Rust has no implicit + /// conversions, so the generic one is no use. + /// + /// Node-handle overloads are left out; they'd need wrappers for `extract()` and the + /// node-handle type. + pub fn insert(&mut self, value: KeyValue) -> (StdUnorderedMapIter<*mut V>, bool) { + self.insert_value(value) + } + + pub fn insert(&mut self, hint: &StdUnorderedMapIter<*const V>, value: KeyValue) -> StdUnorderedMapIter<*mut V> { + self.insert_hint(hint, value) + } + + pub fn insert(&mut self, items: &[KeyValue]) { + self.insert_slice(items) + } + + /// Insert pairs from `[first, last)`, skipping keys that are already in the map. + + pub fn insert(&mut self, first: &StdUnorderedMapIter<*const V>, last: &StdUnorderedMapIter<*const V>) { + self.insert_cpp_iter(first, last) + } + + /// Insert the pairs if their keys are not already in the map. + /// + /// ### Limitations: Only two pairs, same as the `new(kv, kv)` overload. + pub fn insert(&mut self, a: KeyValue, b: KeyValue) { + self.insert_initializer_list_2(a, b) + } + } +} + +// The `erase(...)` Modifiers +overload! { + impl StdUnorderedMap { + /// Removing the element at `pos` and return an iterator to the next element. + pub fn erase(&mut self, pos: StdUnorderedMapIter<*mut V>) -> StdUnorderedMapIter<*mut V> { + self.erase_iter(pos) + } + + pub fn erase(&mut self, pos: StdUnorderedMapIter<*const V>) -> StdUnorderedMapIter<*mut V> { + self.erase_const_iter(pos) + } + + pub fn erase(&mut self, first: StdUnorderedMapIter<*const V>, last: StdUnorderedMapIter<*const V>) -> StdUnorderedMapIter<*mut V> { + self.erase_range(first, last) + } + + /// Removing the element with `key` and return how many elements were removed (0 or 1). + /// ### Limitations: C++23 can erase by other key types when `Hash` and `KeyEqual` are transparent; that needs a recent standard library. + pub fn erase(&mut self, key: K) -> c_size_t { + self.erase_key(key) + } + } +} + +/// A helper for creating key-value pairs in tests. +#[cfg(test)] +fn kv(key: K, value: V) -> KeyValue { + KeyValue { key, value } +} + +/// Check that the expected Rust and C++ constructors are called. +#[test] +pub fn test_unordered_map_new() { + let empty = StdUnorderedMap::new(); + assert_eq!(empty.len(), 0); + + // The actual bucket count depends on the C++ library, so only check that it is at least 100. + let with_buckets: StdUnorderedMap = StdUnorderedMap::new(100_usize); + assert_eq!(with_buckets.len(), 0); + assert!(with_buckets.bucket_count() >= 100); + assert!(empty.bucket_count() < 100); + + let pairs = [kv(1, 10), kv(2, 20)]; + let from_slice = StdUnorderedMap::new(pairs.as_slice()); + assert_eq!(from_slice.len(), 2); + assert_eq!(from_slice.get(1), Some(&10)); + assert_eq!(from_slice.get(2), Some(&20)); + let from_empty_slice = StdUnorderedMap::new([].as_slice()); + assert_eq!(from_empty_slice.len(), 0); + + let from_cpp_iter = StdUnorderedMap::new(&from_slice.begin_const(), &from_slice.end_const()); + assert_eq!(from_cpp_iter.len(), 2); + assert_eq!(from_cpp_iter.get(2), Some(&20)); + + let mut iter = pairs.into_iter(); + let from_iter = StdUnorderedMap::new(&mut iter as &mut dyn Iterator); + assert_eq!(from_iter.len(), 2); + assert_eq!(from_iter.get(1), Some(&10)); + let mut empty_iter = std::iter::empty(); + let from_empty_iter = StdUnorderedMap::new(&mut empty_iter as &mut dyn Iterator); + assert_eq!(from_empty_iter.len(), 0); + + let mut original = StdUnorderedMap::new(pairs.as_slice()); + let copied = StdUnorderedMap::new(&original); + assert_eq!(copied.len(), 2); + // Copying should not change the original map ie the one we copied from. + assert_eq!(original.len(), 2); + let moved = StdUnorderedMap::new(&mut original); + assert_eq!(moved.len(), 2); + assert_eq!(moved.get(1), Some(&10)); + // C++ leaves the moved-from map in a valid but unspecified state. libstdc++ empties it here... + assert_eq!(original.len(), 0); + + let from_initializer_list: StdUnorderedMap = StdUnorderedMap::new(kv(7, 70)); + assert_eq!(from_initializer_list.len(), 1); + assert_eq!(from_initializer_list.get(7), Some(&70)); + let from_initializer_list: StdUnorderedMap = StdUnorderedMap::new(kv(7, 70), kv(8, 80)); + assert_eq!(from_initializer_list.len(), 2); + assert_eq!(from_initializer_list.get(8), Some(&80)); +} + +/// Check that the expected `find(...)` and `max_load_factor(...)` overloads are called. +#[test] +pub fn test_unordered_map_accessors() { + let mut map = StdUnorderedMap::new([kv(1, 10), kv(2, 20)].as_slice()); + + let found = StdUnorderedMap::find(&map, 1); + assert!(!map.is_end_const(&found)); + assert_eq!(unsafe { found.entry() }, kv(1, 10)); + let missing = StdUnorderedMap::find(&map, 99); + assert!(map.is_end_const(&missing)); + + let found_mut = StdUnorderedMap::find(&mut map, 2); + assert!(!map.is_end_mut(&found_mut)); + assert_eq!(unsafe { found_mut.entry() }, kv(2, 20)); + let found_const = found_mut.downgrade(); + assert_eq!(unsafe { found_const.entry() }, kv(2, 20)); + let missing_mut = StdUnorderedMap::find(&mut map, 99); + assert!(map.is_end_mut(&missing_mut)); + + assert_eq!(StdUnorderedMap::max_load_factor(&map), 1.0); + StdUnorderedMap::max_load_factor(&mut map, 0.5); + assert_eq!(StdUnorderedMap::max_load_factor(&map), 0.5); +} + +/// Check that the expected Rust and C++ `insert(...)` overloads are called. +#[test] +pub fn test_unordered_map_insert() { + let mut map = StdUnorderedMap::new(); + + let (iter, inserted) = map.insert(kv(1, 10)); + assert!(inserted); + assert_eq!(unsafe { iter.entry() }, kv(1, 10)); + + let (iter, inserted) = map.insert(kv(1, 99)); + assert!(!inserted); + assert_eq!(unsafe { iter.entry() }, kv(1, 10)); + + let hint = map.end_const(); + let iter = map.insert(&hint, kv(2, 20)); + assert_eq!(unsafe { iter.entry() }, kv(2, 20)); + let hint = map.begin_const(); + let iter = map.insert(&hint, kv(2, 99)); + assert_eq!(unsafe { iter.entry() }, kv(2, 20)); + + map.insert([kv(3, 30), kv(4, 40)].as_slice()); + assert_eq!(map.len(), 4); + assert_eq!(map.get(4), Some(&40)); + map.insert([].as_slice()); + assert_eq!(map.len(), 4); + + let other = StdUnorderedMap::new(kv(5, 50), kv(1, 99)); + map.insert(&other.begin_const(), &other.end_const()); + assert_eq!(map.len(), 5); + assert_eq!(map.get(5), Some(&50)); + assert_eq!(map.get(1), Some(&10)); + + map.insert(kv(6, 60), kv(7, 70)); + assert_eq!(map.len(), 7); + assert_eq!(map.get(7), Some(&70)); +} + +/// Check that the expected Rust and C++ `erase(...)` overloads are called. +#[test] +pub fn test_unordered_map_erase() { + let mut map = StdUnorderedMap::new([kv(1, 10), kv(2, 20), kv(3, 30), kv(4, 40)].as_slice()); + + assert_eq!(map.erase(1), 1); + assert_eq!(map.erase(1), 0); + assert_eq!(map.len(), 3); + + let pos = StdUnorderedMap::find(&mut map, 2); + let _next = map.erase(pos); + assert_eq!(map.get(2), None); + + let pos = StdUnorderedMap::find(&map, 3); + let _next = map.erase(pos); + assert_eq!(map.get(3), None); + assert_eq!(map.len(), 1); + + // Removing the last element returns an `end` iterator. + let pos = StdUnorderedMap::find(&mut map, 4); + let next = map.erase(pos); + assert!(map.is_end_mut(&next)); + assert_eq!(map.len(), 0); + + let mut map = StdUnorderedMap::new([kv(1, 10), kv(2, 20), kv(3, 30)].as_slice()); + let first = map.begin_const(); + let last = map.end_const(); + let next = map.erase(first, last); + assert!(map.is_end_mut(&next)); + assert_eq!(map.len(), 0); +} diff --git a/cpp-overloads-in-rust.md b/cpp-overloads-in-rust.md index 0656300..b9b321b 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] | ✅ | ✅ | | | +| [unordered_map] | ✅ | 🌗⚙️[^5] | 🌗⚙️ | | | [unordered_multiset] | ✅ | ✅⚙️[^3] | ✅ | ?[^4] | | TODO | ? | ? | ? | ? | @@ -38,12 +39,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 +[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. [^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`. ## Limitations