Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions cpp-overload-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
pub mod stdcpp {
// Add new modules for each type here, in alphabetical order.
pub mod forward_list;
pub mod unordered_multiset;
}
212 changes: 212 additions & 0 deletions cpp-overload-test/src/stdcpp/unordered_multiset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//! 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.
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 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.
/// Rust does not have default function arguments, so C++ constructors with default arguments
/// (e.g., `unordered_multiset(size_type bucket_count, const Hash& hash = Hash())`)
/// require separate overloads for each argument combination.
pub fn new() -> StdUnorderedMultiset {
StdUnorderedMultiset::default()
}

/// Construct a set with at least `bucket_count` buckets.
pub fn new(bucket_count: c_size_t) -> StdUnorderedMultiset {
StdUnorderedMultiset::from_bucket_count(bucket_count)
}

/// 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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check that an overload set works in Rust, we need to wrap all of the C++ constructor overloads.

There are 14 constructor overloads in C++, excluding overloads that only differ by an allocator argument:
https://en.cppreference.com/cpp/container/unordered_multiset/unordered_multiset

That's a lot of overloads, so you don't have to implement them all. Instead, pick overloads that are very different from each other. For example, you could add one overload starting with these arguments:

  • bucket_count
  • unordered_multiset
  • from_range_t

It would also be interesting to use C++ default arguments to call an overload with just one argument of type:

  • hash
  • key_equal

This would be enough to cover the constructor overload set reasonably well.

Please document the lack of default argument values in Rust as a limitation.

StdUnorderedMultiset::from_initializer_list_2(a, b)
}
}
}

overload! {
impl StdUnorderedMultiset {
/// Insert a single value into the set.
pub fn insert(&mut self, value: T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of insert overloads as well, feel free to update this PR in stages, and ask for a review after every stage.

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)
}
}
}

#[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);
}