From 4540fd4dafa2772775ee1a51d492f932363be847 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 22 Sep 2026 17:54:27 +0200 Subject: [PATCH 1/6] Add iterator and range overloads --- .github/workflows/check.yml | 29 +++++- cpp-overload-test/build.rs | 5 +- cpp-overload-test/src/stdcpp/forward_list.rs | 97 ++++++++++++++++---- 3 files changed, 110 insertions(+), 21 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4482b7a..6a5150a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -32,6 +32,8 @@ jobs: test-rust: name: 2. Test Rust crates runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -44,15 +46,17 @@ jobs: cargo test --doc --all-features run: - name: 3. Run Rust example binaries + name: 3. Run Rust overload-test binaries runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: toolchain: nightly components: cargo,clippy,rustfmt - - name: Run Rust example binaries + - name: Run Rust overload-test binaries # Find all test crate binaries, extract the basename (no directories or extensions), # then run them one at a time with cargo. # There is no default-run binary set in Cargo.toml. @@ -100,6 +104,8 @@ jobs: publish-dry-run: name: 7. Dry run publishing runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -112,6 +118,8 @@ jobs: diagnostics-rust: name: 8. Diagnostics tests on Rust crates runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -150,7 +158,10 @@ jobs: cpp-overload-test-ok: name: 9. Check C++ overloads - runs-on: ubuntu-latest + # Ubuntu 26.04 is needed for C++23 standard library features. + runs-on: ubuntu-26.04 + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -161,6 +172,12 @@ jobs: # FIXME: turn this into a script so contributors can run it locally. - name: Check C++ overload ok cases run: | + echo "Checking the crate library builds on a newer platform:" + cargo build --all-targets --all-features + cargo clippy --all-targets --all-features -- --deny warnings + cargo test --all-targets --all-features + cargo test --doc --all-features + echo "Checking the C++ overload test crate builds:" cargo build --all-features --package cpp-overload-test --lib cargo clippy --all-features --package cpp-overload-test -- --deny warnings cargo test --all-features --package cpp-overload-test --lib @@ -169,7 +186,10 @@ jobs: cpp-overload-test-fail: name: 10. Must-fail C++ overloads - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 + # Don't run unless the test crate has built successfully, crates arealways built for examples. + needs: + - cpp-overload-test-ok steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -182,6 +202,7 @@ jobs: # and disable color output. # FIXME: turn this into a script so contributors can run it locally. run: | + echo "Checking must-fail example builds:" export CARGO_TERM_COLOR=never set -o pipefail TEST_NAMES=$(find cpp-overload-test/examples -name "*.rs" | \ diff --git a/cpp-overload-test/build.rs b/cpp-overload-test/build.rs index 507c620..307c9c0 100644 --- a/cpp-overload-test/build.rs +++ b/cpp-overload-test/build.rs @@ -1,5 +1,8 @@ fn main() { // `cpp_build` doesn't work with multiple example/binary builds in the same crate, so we use // lib.rs submodules for successful overloads. - cpp_build::build("src/lib.rs"); + cpp_build::Config::new() + // Required for range overloads. + .flag("-std=c++23") + .build("src/lib.rs"); } diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index 075b27e..270065d 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -8,6 +8,8 @@ use std::ffi::{c_int, c_void}; // C++ header includes cpp! {{ #include + #include + #include }} /// A wrapper struct to hold the returned C++ pointer. @@ -25,19 +27,65 @@ impl StdForwardList { StdForwardList(list) } - fn with_capacity(capacity: usize) -> StdForwardList { + fn repeat_default(count: usize) -> StdForwardList { let list = unsafe { - cpp!([capacity as "size_t"] -> *mut c_void as "std::forward_list*" { - return new std::forward_list(capacity); + cpp!([count as "size_t"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list(count); }) }; StdForwardList(list) } - fn repeat_with(value: c_int, capacity: usize) -> StdForwardList { + fn repeat_with(value: c_int, count: usize) -> StdForwardList { let list = unsafe { - cpp!([value as "int", capacity as "size_t"] -> *mut c_void as "std::forward_list*" { - return new std::forward_list(value, capacity); + cpp!([value as "int", count as "size_t"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list(value, count); + }) + }; + StdForwardList(list) + } + + /// ### Limitations + /// + /// The slice satisfies C++ `ContiguousIterator`, even though it's not a requirement for this + /// C++ iterator overload. + /// FIXME: allow non-contiguous iterators, if that makes sense in Rust. + fn from_slice(items: &[c_int]) -> StdForwardList { + let len = items.len(); + let items: *const c_int = items.as_ptr(); + let list = unsafe { + cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::forward_list*" { + // In C++, `items + len` increments the pointer by `len` elements of `sizeof(int)`. + return new std::forward_list(items, items + len); + }) + }; + StdForwardList(list) + } + + /// ### Limitations + /// + /// `std::from_range` is used as a tag to disambiguate this constructor, it is part of the C++ + /// standard library API. The Rust overload needs no tag, so it is more ergonomic. + /// + /// Rust does not allow `impl Iterator` in this position, so we use generics instead. + /// + /// This implementation builds a Rust `Vec` and C++ `std::vector` from the iterator for simplicity. + /// A production implementation could use a Rust-to-C++ iterator-to-range adapter. + fn from_iter(iter: &mut dyn Iterator) -> StdForwardList { + let items: Vec = iter.collect(); + let len = items.len(); + let items: *const c_int = items.as_ptr(); + let list = unsafe { + cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::forward_list*" { + // Use the legacy iterator constructor for `vector`. + // In C++, `items + len` increments the pointer by `len` elements of `sizeof(int)`. + std::vector vec = std::vector(items, items + len); + // `vector` is a range, so we can use it to construct a `forward_list`. + #ifdef __cpp_lib_containers_ranges + return new std::forward_list(std::from_range, vec); + #else + #error "std::from_range is not available, try using `-std=c++23` or a newer compiler" + #endif }) }; StdForwardList(list) @@ -75,29 +123,46 @@ overload! { StdForwardList::default() } - /// Construct a list with a given capacity. - fn new(capacity: usize) -> StdForwardList { - StdForwardList::with_capacity(capacity) + /// Construct a list with `count` default-constructed items. + fn new(count: usize) -> StdForwardList { + StdForwardList::repeat_default(count) + } + + /// Construct a list filled with `count` instances of the given value. + fn new(value: c_int, count: usize) -> StdForwardList { + StdForwardList::repeat_with(value, count) + } + + /// Construct a list by copying items from a slice. + fn new(items: &[c_int]) -> StdForwardList { + StdForwardList::from_slice(items) } - /// Construct a list filled with the given value. - fn new(value: c_int, capacity: usize) -> StdForwardList { - StdForwardList::repeat_with(value, capacity) + /// Construct a list from an iterator. + /// + /// ### Limitations + /// + /// `&mut dyn Iterator` (and `&mut Iterator`) can never be the same type as `&[T]`, + /// but passing an owned `impl Iterator` here could overlap with the `&[T]` overload. + /// + /// The `overload!` macro doesn't support generics in this position yet, so we use + /// `dyn Trait` instead. + /// FIXME: preserve function item-level generics in the macro. + fn new(iter: &mut dyn Iterator) -> StdForwardList { + StdForwardList::from_iter(iter) } - /// Clone a list from a shared reference. + /// Clone list items into a new list from a shared list reference. fn new(other: &StdForwardList) -> StdForwardList { StdForwardList::copy_from(other) } - /// Move list items from a mutable reference into a new list. + /// Move list items into a new list from a mutable list reference. fn new(other: &mut StdForwardList) -> StdForwardList { StdForwardList::move_from(other) } // TODO: - // iterator - // range // initializer_list } } From dbbce723bbfcd3c0608dc2fc923e8810a789c6a9 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 22 Sep 2026 20:04:49 +0200 Subject: [PATCH 2/6] Note non-generic limitation --- cpp-overload-test/src/stdcpp/forward_list.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index 270065d..07a5e44 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -13,6 +13,11 @@ cpp! {{ }} /// A wrapper struct to hold the returned C++ pointer. +/// +/// ### Limitations +/// +/// This struct and the impl should be fully generic over the list type. +/// FIXME: try this and see if the `overload!` macro and `splat` feature can handle it. struct StdForwardList(*mut c_void); // The cpp! macro doesn't work inside the overload! macro, so we extract C++ calls into separate methods. From aca84ebbb1e778e16765b61c4853fb486e5541dd Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 22 Sep 2026 20:23:22 +0200 Subject: [PATCH 3/6] Ignore a C++ deprecation in the cpp crate --- cpp-overload-test/build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp-overload-test/build.rs b/cpp-overload-test/build.rs index 307c9c0..5cf150f 100644 --- a/cpp-overload-test/build.rs +++ b/cpp-overload-test/build.rs @@ -4,5 +4,7 @@ fn main() { cpp_build::Config::new() // Required for range overloads. .flag("-std=c++23") + // Ignore `warning: 'std::rel_ops' is deprecated: use '<=>' instead [-Wdeprecated-declarations]` + .flag("-Wno-deprecated-declarations") .build("src/lib.rs"); } From b06497eebf826656eb73a9092e5d1ae32558d054 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 22 Sep 2026 20:37:44 +0200 Subject: [PATCH 4/6] Add initializer-list overload --- cpp-overload-test/src/stdcpp/forward_list.rs | 32 ++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index 07a5e44..32997f9 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -117,6 +117,18 @@ impl StdForwardList { }; StdForwardList(list) } + + /// ### Limitations + /// + /// This is just an example, a production implementation would support any number of arguments. + fn from_initializer_list(a: c_int, b: c_int, c: c_int) -> StdForwardList { + let list = unsafe { + cpp!([a as "int", b as "int", c as "int"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list{ a, b, c }; + }) + }; + StdForwardList(list) + } } // We ignore constructors that only differ by an allocator argument, because they're not @@ -167,8 +179,23 @@ overload! { StdForwardList::move_from(other) } - // TODO: - // initializer_list + /// Construct a list from the supplied items. + /// + /// ### Limitations + /// + /// This is just an example, a production implementation would support any number of arguments. + /// Rust doesn't have a language equivalent to C++'s `initializer_list`, and it doesn't + /// have variadic tuples, so we can't overload on the tuple trait itself. + /// + /// The following numbers of argument clash with other overloads: + /// - 0: the default no-argument constructor + /// - 1: the `count` default-value repetition constructor, if `T` is `size_t` + /// - most constructors take 1 argument, so there could be other clashes in unusual + /// circumstances. + /// - 2: the `value` repetition constructor, if `T` is `size_t` + fn new(a: c_int, b: c_int, c: c_int) -> StdForwardList { + StdForwardList::from_initializer_list(a, b, c) + } } } @@ -178,4 +205,5 @@ pub fn test_forward_list() { let _repeat_with = StdForwardList::new(42, 100); let _ref_clone = StdForwardList::new(&default_list); let _mut_clone = StdForwardList::new(&mut default_list); + let _from_initializer_list = StdForwardList::new(1, 2, 3); } From a5780f867433e5e20aab307aa5bedd8aaa135c2d Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 22 Sep 2026 20:37:56 +0200 Subject: [PATCH 5/6] Add missing overload calls --- cpp-overload-test/src/stdcpp/forward_list.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index 32997f9..f10d66a 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -201,8 +201,10 @@ overload! { pub fn test_forward_list() { let mut default_list = StdForwardList::new(); - let _with_capacity = StdForwardList::new(10); + let _repeat_default = StdForwardList::new(10); let _repeat_with = StdForwardList::new(42, 100); + let _from_slice = StdForwardList::new(&[1, 2, 3]); + let _from_iter = StdForwardList::new(&mut (0..10).into_iter()); let _ref_clone = StdForwardList::new(&default_list); let _mut_clone = StdForwardList::new(&mut default_list); let _from_initializer_list = StdForwardList::new(1, 2, 3); From 17fede9e236f1d76ee5b949c8d1eb1dd0036e232 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 22 Sep 2026 20:48:28 +0200 Subject: [PATCH 6/6] Tweak limitations docs --- cpp-overload-test/src/stdcpp/forward_list.rs | 26 ++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index f10d66a..a998ef1 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -70,9 +70,8 @@ impl StdForwardList { /// ### Limitations /// /// `std::from_range` is used as a tag to disambiguate this constructor, it is part of the C++ - /// standard library API. The Rust overload needs no tag, so it is more ergonomic. - /// - /// Rust does not allow `impl Iterator` in this position, so we use generics instead. + /// standard library API. The Rust overload requires a cast to `dyn Iterator` instead, but this + /// is likely just a macro limitation. /// /// This implementation builds a Rust `Vec` and C++ `std::vector` from the iterator for simplicity. /// A production implementation could use a Rust-to-C++ iterator-to-range adapter. @@ -164,7 +163,7 @@ overload! { /// /// The `overload!` macro doesn't support generics in this position yet, so we use /// `dyn Trait` instead. - /// FIXME: preserve function item-level generics in the macro. + /// FIXME: make the macro support generic iterators, or handle `impl Iterator` correctly. fn new(iter: &mut dyn Iterator) -> StdForwardList { StdForwardList::from_iter(iter) } @@ -187,7 +186,7 @@ overload! { /// Rust doesn't have a language equivalent to C++'s `initializer_list`, and it doesn't /// have variadic tuples, so we can't overload on the tuple trait itself. /// - /// The following numbers of argument clash with other overloads: + /// The following initializer argument counts clash with other overloads: /// - 0: the default no-argument constructor /// - 1: the `count` default-value repetition constructor, if `T` is `size_t` /// - most constructors take 1 argument, so there could be other clashes in unusual @@ -203,8 +202,21 @@ pub fn test_forward_list() { let mut default_list = StdForwardList::new(); let _repeat_default = StdForwardList::new(10); let _repeat_with = StdForwardList::new(42, 100); - let _from_slice = StdForwardList::new(&[1, 2, 3]); - let _from_iter = StdForwardList::new(&mut (0..10).into_iter()); + + // ### Limitations + // + // The `as_slice` call is required to match the overload, an array doesn't automatically coerce. + // FIXME: maybe add a const generic overload for arrays. + let _from_slice = StdForwardList::new([1, 2, 3].as_slice()); + + // ### Limitations + // + // The cast is required to match the overload, an iterator doesn't automatically coerce. + // It would be more ergonomic for users to collect the iterator themselves, then use the slice + // overload, or create a Rust/C++ iterator-to-range adapter. + let mut iter = [1, 2, 3].iter().copied(); + let _from_iter = StdForwardList::new(&mut iter as &mut dyn Iterator); + let _ref_clone = StdForwardList::new(&default_list); let _mut_clone = StdForwardList::new(&mut default_list); let _from_initializer_list = StdForwardList::new(1, 2, 3);