Skip to content

Commit 0ef6b8e

Browse files
authored
Add unordered_multiset overloading analysis
1 parent f9b9f90 commit 0ef6b8e

5 files changed

Lines changed: 333 additions & 0 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! Test C++ [std::unordered_multiset][unordered_multiset] overloads that can't be represented in Rust.
2+
//! Working overloads are in [unordered_multiset.rs].
3+
//!
4+
//! [unordered_multiset]: https://en.cppreference.com/cpp/container/unordered_multiset
5+
//! [unordered_multiset.rs]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/unordered_multiset.rs
6+
//!
7+
//! The `cpp!` macro doesn't work here because examples are built separately from `lib.rs`.
8+
9+
#![feature(splat, tuple_trait, c_size_t)]
10+
#![allow(incomplete_features)]
11+
#![allow(unused_braces)]
12+
13+
use splat_overload::overload;
14+
use std::ffi::c_void;
15+
use std::marker::PhantomData;
16+
17+
#[expect(dead_code, reason = "Incompatible overloads can't be used")]
18+
struct StdUnorderedMultiset(*mut c_void, PhantomData<T>);
19+
20+
type T = std::ffi::c_int;
21+
22+
// The overload! macro can't overload on &self vs &mut self
23+
overload! {
24+
impl StdUnorderedMultiset {
25+
/// ### Incompatibilities
26+
///
27+
/// The overload! macro can't overload on `&self` vs `&mut self`, because it has to
28+
/// dispatch the overload through a single function, which can only have one receiver type.
29+
fn begin(&self) where T: Sized {
30+
todo!()
31+
}
32+
33+
fn begin(&mut self) where T: Sized {
34+
todo!()
35+
}
36+
}
37+
}
38+
39+
pub fn main() {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: proc macro panicked
2+
--> cpp-overload-test/examples/stdcpp-unordered-multiset.rs:23:1
3+
|
4+
23 | / overload! {
5+
24 | | impl StdUnorderedMultiset {
6+
25 | | /// ### Incompatibilities
7+
26 | | ///
8+
... |
9+
37 | | }
10+
| |_^
11+
|
12+
= help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got begin: &self and begin: &mut self
13+
14+
error: could not compile `cpp-overload-test` (example "stdcpp-unordered-multiset") due to 1 previous error

‎cpp-overload-test/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod stdcpp {
1111
// Add new modules for each type here, in alphabetical order.
1212
pub mod forward_list;
1313
pub mod hash;
14+
pub mod unordered_multiset;
1415
}
1516

1617
// Checks that `build.rs` rebuilds changed `cpp!` code, see `rerun-check.sh`.
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
//! Test that C++ overloads for [std::unordered_multiset][unordered_multiset] can be successfully represented in Rust.
2+
//!
3+
//! [unordered_multiset]: https://en.cppreference.com/cpp/container/unordered_multiset
4+
5+
#![allow(unused_braces, reason = "Generated by overload! macro")]
6+
7+
use cpp::cpp;
8+
use splat_overload::overload;
9+
use std::ffi::{c_char, c_size_t, c_void};
10+
use std::marker::PhantomData;
11+
12+
// C++ header includes
13+
cpp! {{
14+
#include <unordered_set>
15+
#include <vector>
16+
#include <cstddef>
17+
}}
18+
19+
/// A wrapper struct to hold the returned C++ `std::unordered_multiset<int>` pointer.
20+
///
21+
/// ### Limitations
22+
///
23+
/// This struct and the impl should be fully generic over the set type.
24+
/// FIXME: make the `overload!` macro support generics.
25+
#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))]
26+
pub struct StdUnorderedMultiset(*mut c_void, PhantomData<T>);
27+
28+
/// Workaround for missing generic support in the `overload!` macro.
29+
///
30+
/// The `overload!` macro does not support generics, so the implementation is hardcoded to `int` type.
31+
pub type T = std::ffi::c_int;
32+
33+
#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))]
34+
#[allow(
35+
non_upper_case_globals,
36+
reason = "Triggered by cpp! macro in rust-analyzer"
37+
)]
38+
impl StdUnorderedMultiset {
39+
fn default() -> StdUnorderedMultiset {
40+
let set = unsafe {
41+
cpp!([] -> *mut c_void as "std::unordered_multiset<int>*" {
42+
return new std::unordered_multiset<int>();
43+
})
44+
};
45+
StdUnorderedMultiset(set, PhantomData)
46+
}
47+
48+
fn from_bucket_count(bucket_count: c_size_t) -> StdUnorderedMultiset {
49+
let set = unsafe {
50+
cpp!([bucket_count as "size_t"] -> *mut c_void as "std::unordered_multiset<int>*" {
51+
return new std::unordered_multiset<int>(bucket_count);
52+
})
53+
};
54+
StdUnorderedMultiset(set, PhantomData)
55+
}
56+
57+
fn copy_from(other: &StdUnorderedMultiset) -> StdUnorderedMultiset {
58+
let other_ptr = other.0 as *const c_void;
59+
let set = unsafe {
60+
cpp!([other_ptr as "const std::unordered_multiset<int>*"] -> *mut c_void as "std::unordered_multiset<int>*" {
61+
return new std::unordered_multiset<int>(*other_ptr);
62+
})
63+
};
64+
StdUnorderedMultiset(set, PhantomData)
65+
}
66+
67+
fn from_slice(items: &[T]) -> StdUnorderedMultiset {
68+
let len = items.len();
69+
let items: *const T = items.as_ptr();
70+
let set = unsafe {
71+
cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::unordered_multiset<int>*" {
72+
return new std::unordered_multiset<int>(items, items + len);
73+
})
74+
};
75+
StdUnorderedMultiset(set, PhantomData)
76+
}
77+
78+
fn from_initializer_list_2(a: T, b: T) -> StdUnorderedMultiset {
79+
let set = unsafe {
80+
cpp!([a as "int", b as "int"] -> *mut c_void as "std::unordered_multiset<int>*" {
81+
return new std::unordered_multiset<int>{ a, b };
82+
})
83+
};
84+
StdUnorderedMultiset(set, PhantomData)
85+
}
86+
87+
fn insert_value(&mut self, value: T) {
88+
let slf: *mut c_void = self.0;
89+
unsafe {
90+
cpp!([slf as "std::unordered_multiset<int>*", value as "int"] {
91+
slf->insert(value);
92+
})
93+
}
94+
}
95+
96+
fn insert_range(&mut self, items: &[T]) {
97+
let slf: *mut c_void = self.0;
98+
let len = items.len();
99+
let items: *const T = items.as_ptr();
100+
unsafe {
101+
cpp!([slf as "std::unordered_multiset<int>*", items as "const int*", len as "size_t"] {
102+
slf->insert(items, items + len);
103+
})
104+
}
105+
}
106+
107+
fn insert_initializer_list_2(&mut self, a: T, b: T) {
108+
let slf: *mut c_void = self.0;
109+
unsafe {
110+
cpp!([slf as "std::unordered_multiset<int>*", a as "int", b as "int"] {
111+
slf->insert({ a, b });
112+
})
113+
}
114+
}
115+
116+
fn insert_with_hint(&mut self, hint: c_size_t, value: T) {
117+
let slf: *mut c_void = self.0;
118+
unsafe {
119+
cpp!([slf as "std::unordered_multiset<int>*", hint as "size_t", value as "int"] {
120+
auto it = slf->begin();
121+
std::advance(it, hint);
122+
slf->insert(it, value);
123+
})
124+
}
125+
}
126+
127+
fn count(&self, value: T) -> c_size_t {
128+
let slf = self.0 as *const c_void;
129+
unsafe {
130+
cpp!([slf as "const std::unordered_multiset<int>*", value as "int"] -> c_size_t as "size_t" {
131+
return slf->count(value);
132+
})
133+
}
134+
}
135+
136+
fn is_empty(&self) -> bool {
137+
let slf = self.0 as *const c_void;
138+
let empty = unsafe {
139+
cpp!([slf as "const std::unordered_multiset<int>*"] -> c_char as "char" {
140+
return slf->empty();
141+
})
142+
};
143+
empty != 0
144+
}
145+
}
146+
147+
overload! {
148+
impl StdUnorderedMultiset {
149+
/// Construct an empty set.
150+
///
151+
/// ### Limitations
152+
///
153+
/// The `overload!` macro does not support generics, so the implementation is hardcoded to `int` type.
154+
pub fn new() -> StdUnorderedMultiset {
155+
StdUnorderedMultiset::default()
156+
}
157+
158+
/// Construct a set with at least `bucket_count` buckets.
159+
///
160+
/// ### Limitations
161+
///
162+
/// Rust doesn't have default function arguments. In C++, this constructor accepts
163+
/// optional hash and key_equal arguments with default values. In Rust, each argument
164+
/// combination would require a separate overload, which becomes unwieldy.
165+
pub fn new(bucket_count: c_size_t) -> StdUnorderedMultiset {
166+
StdUnorderedMultiset::from_bucket_count(bucket_count)
167+
}
168+
169+
/// Construct a set with at least `bucket_count` buckets and a custom hash function.
170+
///
171+
/// FIXME: This overload can be implemented using the same pattern as BinaryPredicateRust
172+
/// in forward_list. The C++ function type needs to be declared in a cpp! block.
173+
pub fn new(_bucket_count: c_size_t, _hash_fn: extern "C" fn(&T) -> c_size_t) -> StdUnorderedMultiset {
174+
todo!()
175+
}
176+
177+
/// Construct a set with at least `bucket_count` buckets and a custom key equality function.
178+
///
179+
/// FIXME: This overload can be implemented using the same pattern as BinaryPredicateRust
180+
/// in forward_list. The C++ function type needs to be declared in a cpp! block.
181+
pub fn new(_bucket_count: c_size_t, _key_equal: extern "C" fn(&T, &T) -> bool) -> StdUnorderedMultiset {
182+
todo!()
183+
}
184+
185+
/// Construct a set by copying from another set.
186+
pub fn new(other: &StdUnorderedMultiset) -> StdUnorderedMultiset {
187+
StdUnorderedMultiset::copy_from(other)
188+
}
189+
190+
/// Construct a set by copying items from a slice.
191+
///
192+
/// ### Ergonomics
193+
///
194+
/// The `as_slice()` call is required to match the slice overload, as arrays don't automatically coerce.
195+
pub fn new(items: &[T]) -> StdUnorderedMultiset {
196+
StdUnorderedMultiset::from_slice(items)
197+
}
198+
199+
/// Construct a set from an initializer list.
200+
///
201+
/// ### Ergonomics
202+
///
203+
/// Initializer list overloads require separate functions for each argument count, which is not ergonomic.
204+
pub fn new(a: T, b: T) -> StdUnorderedMultiset {
205+
StdUnorderedMultiset::from_initializer_list_2(a, b)
206+
}
207+
}
208+
}
209+
210+
overload! {
211+
impl StdUnorderedMultiset {
212+
/// Insert a single value into the set.
213+
pub fn insert(&mut self, value: T) {
214+
self.insert_value(value)
215+
}
216+
217+
/// Insert a range of values from a slice into the set.
218+
///
219+
/// ### Ergonomics
220+
///
221+
/// The `as_slice()` call is required to match the slice overload, as arrays don't automatically coerce.
222+
pub fn insert(&mut self, items: &[T]) {
223+
self.insert_range(items)
224+
}
225+
226+
/// Insert values from an initializer list.
227+
///
228+
/// ### Ergonomics
229+
///
230+
/// Initializer list overloads require separate functions for each argument count, which is not ergonomic.
231+
pub fn insert(&mut self, a: T, b: T) {
232+
self.insert_initializer_list_2(a, b)
233+
}
234+
235+
/// Insert a value with a position hint.
236+
pub fn insert(&mut self, hint: c_size_t, value: T) {
237+
self.insert_with_hint(hint, value)
238+
}
239+
}
240+
}
241+
242+
#[test]
243+
pub fn test_unordered_multiset() {
244+
let default_set = StdUnorderedMultiset::new();
245+
assert!(default_set.is_empty());
246+
247+
let from_slice = StdUnorderedMultiset::new([1, 2, 3].as_slice());
248+
assert_eq!(from_slice.count(1), 1);
249+
250+
let from_bucket_count = StdUnorderedMultiset::new(10 as c_size_t);
251+
assert!(from_bucket_count.is_empty());
252+
253+
let from_initializer_list = StdUnorderedMultiset::new(2, 1);
254+
assert_eq!(from_initializer_list.count(2), 1);
255+
256+
let empty_set = StdUnorderedMultiset::new();
257+
let copied = StdUnorderedMultiset::new(&empty_set);
258+
assert!(copied.is_empty());
259+
260+
let mut insert_set = StdUnorderedMultiset::new();
261+
insert_set.insert(42);
262+
assert_eq!(insert_set.count(42), 1);
263+
264+
let mut insert_set = StdUnorderedMultiset::new();
265+
insert_set.insert([1, 2, 3].as_slice());
266+
assert_eq!(insert_set.count(1), 1);
267+
268+
let mut insert_init = StdUnorderedMultiset::new();
269+
insert_init.insert(5, 10);
270+
assert_eq!(insert_init.count(5), 1);
271+
272+
let mut insert_hint = StdUnorderedMultiset::new();
273+
insert_hint.insert(0 as c_size_t, 7);
274+
assert_eq!(insert_hint.count(7), 1);
275+
}

‎cpp-overloads-in-rust.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ See the Limitations section below the table for more details.
2828
| *Template Classes* | Constructors | Accessors | Modifiers | Transforms |
2929
| [forward_list] | ✅ | 🌗⚙️[^1] | 🌗⚙️?[^2] | ✅ |
3030
| [hash] | ✅ | ✅ | | |
31+
| [unordered_multiset] | ✅ | ✅⚙️[^3] | ✅ | ?[^4] |
3132
| TODO | ? | ? | ? | ? |
3233

3334
Constructors: Create an instance of the class, called as `Class::Class(...)`<br/>
@@ -37,9 +38,12 @@ Transforms: Rearrange the items in a list class, called as `instance->sort(...)`
3738

3839
[forward_list]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/forward_list.rs
3940
[hash]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/hash.rs
41+
[unordered_multiset]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/unordered_multiset.rs
4042

4143
[^1]: Rust and C++ iterators have different semantics, using adapter types is an ergonomic workaround.
4244
[^2]: `insert_after` and `splice_after` still need to be analysed.
45+
[^3]: The overload macro can't overload on `&self` vs `&mut self`, which limits representing some accessor overloads.
46+
[^4]: `extract`, `merge`, and `erase_if` still need analysis. These are C++17/C++20 node handle and non-member function overloads.
4347

4448
## Limitations
4549

0 commit comments

Comments
 (0)