Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c3f44a0
added unordered_multisetmodule
Lynndabel Oct 13, 2026
371f659
Add unordered_multiset wrapper
Lynndabel Oct 14, 2026
b0c7534
Merge branch 'main' into unordered_multiset
Lynndabel Oct 14, 2026
087664b
Merge branch 'main' into unordered_multiset
Lynndabel Oct 15, 2026
be53e07
Split documentation and add missing constructor overloads
Lynndabel Oct 15, 2026
8c5a097
Merge branch 'main' into unordered_multiset
Lynndabel Oct 16, 2026
ff27429
Improve documentation about Rust default arguments limitation
Lynndabel Oct 16, 2026
597b2cf
Add hash and key_equal constructor overloads with default arguments
Lynndabel Oct 17, 2026
7e354ae
Remove hash and key_equal constructors due to C++ template complexity
Lynndabel Oct 20, 2026
57c9dc8
Merge branch 'main' into unordered_multiset
Lynndabel Oct 25, 2026
96151cf
Document failing overloads for step 2
Lynndabel Oct 25, 2026
27b4afe
Merge branch 'main' into unordered_multiset
Lynndabel Oct 27, 2026
8f280fb
Add more insert overloads and move default args documentation
Lynndabel Oct 27, 2026
e46dbb6
Remove failing overloads section and move to constructor docs
Lynndabel Oct 27, 2026
c1909ee
Move generic limitation comment and add step 2 failing overloads
Lynndabel Oct 27, 2026
c791fc3
Merge branch 'main' into unordered_multiset
Lynndabel Oct 27, 2026
0eb87b0
Add step 2 example file for failing hash/key_equal overloads
Lynndabel Oct 29, 2026
4477558
Merge branch 'main' into unordered_multiset
Lynndabel Oct 30, 2026
93f5e06
Move hash/key_equal to working file and add failing overload example
Lynndabel Oct 31, 2026
bd541f6
Fix stderr file format
Lynndabel Nov 2, 2026
0de1a8a
Add unordered_multiset to compatibility table
Lynndabel Nov 2, 2026
b4625ff
Add missing transforms overloads to unordered_multiset entry
Lynndabel Nov 3, 2026
11d26f7
Regenerate stderr file
Lynndabel Nov 3, 2026
5ce8e6c
Remove Compiling line from stderr to match CI filtering
Lynndabel Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions cpp-overload-test/examples/stdcpp-unordered-multiset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Test C++ [std::unordered_multiset][unordered_multiset] overloads that can't be represented in Rust.
//! Working overloads are in [unordered_multiset.rs].
//!
//! [unordered_multiset]: https://en.cppreference.com/cpp/container/unordered_multiset
//! [unordered_multiset.rs]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/unordered_multiset.rs
//!
//! The `cpp!` macro doesn't work here because examples are built separately from `lib.rs`.

#![feature(splat, tuple_trait, c_size_t)]
#![allow(incomplete_features)]
#![allow(unused_braces)]

use splat_overload::overload;
use std::ffi::c_void;
use std::marker::PhantomData;

#[expect(dead_code, reason = "Incompatible overloads can't be used")]
struct StdUnorderedMultiset(*mut c_void, PhantomData<T>);

type T = std::ffi::c_int;

// The overload! macro can't overload on &self vs &mut self
overload! {
impl StdUnorderedMultiset {
/// ### Incompatibilities
///
/// The overload! macro can't overload on `&self` vs `&mut self`, because it has to
/// dispatch the overload through a single function, which can only have one receiver type.
fn begin(&self) where T: Sized {
todo!()
}

fn begin(&mut self) where T: Sized {
todo!()
}
}
}

pub fn main() {}
14 changes: 14 additions & 0 deletions cpp-overload-test/examples/stdcpp-unordered-multiset.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: proc macro panicked
--> cpp-overload-test/examples/stdcpp-unordered-multiset.rs:23:1
|
23 | / overload! {
24 | | impl StdUnorderedMultiset {
25 | | /// ### Incompatibilities
26 | | ///
... |
37 | | }
| |_^
|
= help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got begin: &self and begin: &mut self

error: could not compile `cpp-overload-test` (example "stdcpp-unordered-multiset") due to 1 previous error
1 change: 1 addition & 0 deletions cpp-overload-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_multiset;
}

// Checks that `build.rs` rebuilds changed `cpp!` code, see `rerun-check.sh`.
Expand Down
275 changes: 275 additions & 0 deletions cpp-overload-test/src/stdcpp/unordered_multiset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
//! Test that C++ overloads for [std::unordered_multiset][unordered_multiset] can be successfully represented in Rust.
//!
//! [unordered_multiset]: https://en.cppreference.com/cpp/container/unordered_multiset

#![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;

// C++ header includes
cpp! {{
#include <unordered_set>
#include <vector>
#include <cstddef>
}}

/// A wrapper struct to hold the returned C++ `std::unordered_multiset<int>` pointer.
///
/// ### Limitations
///
/// This struct and the impl should be fully generic over the set type.
/// FIXME: make the `overload!` macro support generics.
#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))]
pub struct StdUnorderedMultiset(*mut c_void, PhantomData<T>);

/// Workaround for missing generic support in the `overload!` macro.
///
/// The `overload!` macro does not support generics, so the implementation is hardcoded to `int` type.
pub type T = std::ffi::c_int;

#[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 StdUnorderedMultiset {
fn default() -> StdUnorderedMultiset {
let set = unsafe {
cpp!([] -> *mut c_void as "std::unordered_multiset<int>*" {
return new std::unordered_multiset<int>();
})
};
StdUnorderedMultiset(set, PhantomData)
}

fn from_bucket_count(bucket_count: c_size_t) -> StdUnorderedMultiset {
let set = unsafe {
cpp!([bucket_count as "size_t"] -> *mut c_void as "std::unordered_multiset<int>*" {
return new std::unordered_multiset<int>(bucket_count);
})
};
StdUnorderedMultiset(set, PhantomData)
}

fn copy_from(other: &StdUnorderedMultiset) -> StdUnorderedMultiset {
let other_ptr = other.0 as *const c_void;
let set = unsafe {
cpp!([other_ptr as "const std::unordered_multiset<int>*"] -> *mut c_void as "std::unordered_multiset<int>*" {
return new std::unordered_multiset<int>(*other_ptr);
})
};
StdUnorderedMultiset(set, PhantomData)
}

fn from_slice(items: &[T]) -> StdUnorderedMultiset {
let len = items.len();
let items: *const T = items.as_ptr();
let set = unsafe {
cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::unordered_multiset<int>*" {
return new std::unordered_multiset<int>(items, items + len);
})
};
StdUnorderedMultiset(set, PhantomData)
}

fn from_initializer_list_2(a: T, b: T) -> StdUnorderedMultiset {
let set = unsafe {
cpp!([a as "int", b as "int"] -> *mut c_void as "std::unordered_multiset<int>*" {
return new std::unordered_multiset<int>{ a, b };
})
};
StdUnorderedMultiset(set, PhantomData)
}

fn insert_value(&mut self, value: T) {
let slf: *mut c_void = self.0;
unsafe {
cpp!([slf as "std::unordered_multiset<int>*", value as "int"] {
slf->insert(value);
})
}
}

fn insert_range(&mut self, items: &[T]) {
let slf: *mut c_void = self.0;
let len = items.len();
let items: *const T = items.as_ptr();
unsafe {
cpp!([slf as "std::unordered_multiset<int>*", items as "const int*", len as "size_t"] {
slf->insert(items, items + len);
})
}
}

fn insert_initializer_list_2(&mut self, a: T, b: T) {
let slf: *mut c_void = self.0;
unsafe {
cpp!([slf as "std::unordered_multiset<int>*", a as "int", b as "int"] {
slf->insert({ a, b });
})
}
}

fn insert_with_hint(&mut self, hint: c_size_t, value: T) {
let slf: *mut c_void = self.0;
unsafe {
cpp!([slf as "std::unordered_multiset<int>*", hint as "size_t", value as "int"] {
auto it = slf->begin();
std::advance(it, hint);
slf->insert(it, value);
})
}
}

fn count(&self, value: T) -> c_size_t {
let slf = self.0 as *const c_void;
unsafe {
cpp!([slf as "const std::unordered_multiset<int>*", value as "int"] -> c_size_t as "size_t" {
return slf->count(value);
})
}
}

fn is_empty(&self) -> bool {
let slf = self.0 as *const c_void;
let empty = unsafe {
cpp!([slf as "const std::unordered_multiset<int>*"] -> c_char as "char" {
return slf->empty();
})
};
empty != 0
}
}

overload! {
impl StdUnorderedMultiset {
/// Construct an empty set.
///
/// ### Limitations
///
/// The `overload!` macro does not support generics, so the implementation is hardcoded to `int` type.
pub fn new() -> StdUnorderedMultiset {
StdUnorderedMultiset::default()
}

/// Construct a set with at least `bucket_count` buckets.
///
/// ### Limitations
///
/// Rust doesn't have default function arguments. In C++, this constructor accepts
/// optional hash and key_equal arguments with default values. In Rust, each argument
/// combination would require a separate overload, which becomes unwieldy.
pub fn new(bucket_count: c_size_t) -> StdUnorderedMultiset {
StdUnorderedMultiset::from_bucket_count(bucket_count)
}

/// Construct a set with at least `bucket_count` buckets and a custom hash function.
///
/// FIXME: This overload can be implemented using the same pattern as BinaryPredicateRust
/// in forward_list. The C++ function type needs to be declared in a cpp! block.
pub fn new(_bucket_count: c_size_t, _hash_fn: extern "C" fn(&T) -> c_size_t) -> StdUnorderedMultiset {
todo!()
}

/// Construct a set with at least `bucket_count` buckets and a custom key equality function.
///
/// FIXME: This overload can be implemented using the same pattern as BinaryPredicateRust
/// in forward_list. The C++ function type needs to be declared in a cpp! block.
pub fn new(_bucket_count: c_size_t, _key_equal: extern "C" fn(&T, &T) -> bool) -> StdUnorderedMultiset {
todo!()
}

/// Construct a set by copying from another set.
pub fn new(other: &StdUnorderedMultiset) -> StdUnorderedMultiset {
StdUnorderedMultiset::copy_from(other)
}

/// Construct a set by copying items from a slice.
///
/// ### Ergonomics
///
/// The `as_slice()` call is required to match the slice overload, as arrays don't automatically coerce.
pub fn new(items: &[T]) -> StdUnorderedMultiset {
StdUnorderedMultiset::from_slice(items)
}

/// Construct a set from an initializer list.
///
/// ### Ergonomics
///
/// Initializer list overloads require separate functions for each argument count, which is not ergonomic.
pub fn new(a: T, b: T) -> StdUnorderedMultiset {
Comment thread
teor2345 marked this conversation as resolved.
Resolved
StdUnorderedMultiset::from_initializer_list_2(a, b)
}
}
}

overload! {
impl StdUnorderedMultiset {
/// Insert a single value into the set.
pub fn insert(&mut self, value: T) {
Comment thread
teor2345 marked this conversation as resolved.
Resolved
self.insert_value(value)
}

/// Insert a range of values from a slice into the set.
///
/// ### Ergonomics
///
/// The `as_slice()` call is required to match the slice overload, as arrays don't automatically coerce.
pub fn insert(&mut self, items: &[T]) {
self.insert_range(items)
}

/// Insert values from an initializer list.
///
/// ### Ergonomics
///
/// Initializer list overloads require separate functions for each argument count, which is not ergonomic.
pub fn insert(&mut self, a: T, b: T) {
self.insert_initializer_list_2(a, b)
}

/// Insert a value with a position hint.
pub fn insert(&mut self, hint: c_size_t, value: T) {
self.insert_with_hint(hint, value)
}
}
}

#[test]
pub fn test_unordered_multiset() {
let default_set = StdUnorderedMultiset::new();
assert!(default_set.is_empty());

let from_slice = StdUnorderedMultiset::new([1, 2, 3].as_slice());
assert_eq!(from_slice.count(1), 1);

let from_bucket_count = StdUnorderedMultiset::new(10 as c_size_t);
assert!(from_bucket_count.is_empty());

let from_initializer_list = StdUnorderedMultiset::new(2, 1);
assert_eq!(from_initializer_list.count(2), 1);

let empty_set = StdUnorderedMultiset::new();
let copied = StdUnorderedMultiset::new(&empty_set);
assert!(copied.is_empty());

let mut insert_set = StdUnorderedMultiset::new();
insert_set.insert(42);
assert_eq!(insert_set.count(42), 1);

let mut insert_set = StdUnorderedMultiset::new();
insert_set.insert([1, 2, 3].as_slice());
assert_eq!(insert_set.count(1), 1);

let mut insert_init = StdUnorderedMultiset::new();
insert_init.insert(5, 10);
assert_eq!(insert_init.count(5), 1);

let mut insert_hint = StdUnorderedMultiset::new();
insert_hint.insert(0 as c_size_t, 7);
assert_eq!(insert_hint.count(7), 1);
}
4 changes: 4 additions & 0 deletions cpp-overloads-in-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_multiset] | ✅ | ✅⚙️[^3] | ✅ | ?[^4] |
| TODO | ? | ? | ? | ? |

Constructors: Create an instance of the class, called as `Class::Class(...)`<br/>
Expand All @@ -37,9 +38,12 @@ 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

[^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.
Comment thread
teor2345 marked this conversation as resolved.
Resolved
[^4]: `extract`, `merge`, and `erase_if` still need analysis. These are C++17/C++20 node handle and non-member function overloads.

## Limitations

Expand Down
Loading