diff --git a/cpp-overload-test/examples/stdcpp-time-point.rs b/cpp-overload-test/examples/stdcpp-time-point.rs new file mode 100644 index 0000000..139e4f5 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-time-point.rs @@ -0,0 +1,50 @@ +//! Test c++ std::chrono::time_point +//! +//! Working overloads are in [time_point.rs] +//! +//! [time_point.rs]: ../src/stdcpp/time_point.rs + +#![feature(splat, tuple_trait)] +#![allow(incomplete_features)] +#![allow(unused_braces)] + +use splat_overload::overload; +use std::ffi::c_longlong; + +/// A fake Rust wrapper representing a C++ time_point. +/// +/// Using vector to represent the time point because this example only tests +/// overload compatibility. +#[expect(dead_code, reason = "Incompatible overloads can't be used")] +struct StdTimePoint(Vec); + +/// Workaround for testing identical time point types. +/// +/// The converting constructor can accept a time point with the same +/// duration type as the copy constructor. +type SameTimePoint = StdTimePoint; + +// Constructors: new(...) +// +// When copy constructor and converting constructor receive the same +// time_point type, their argument types become identical. +// +// c++ resolves this by preferring the non-template copy constructor. +// Rust cannot represent both as separate overloads. + +overload! { + impl StdTimePoint { + + /// Copy constructor + fn new(_other: &StdTimePoint) -> StdTimePoint { + todo!() + } + + /// Converting constructor + fn new(_other: &SameTimePoint) -> StdTimePoint { + todo!() + } + } +} + +pub fn main() {} diff --git a/cpp-overload-test/examples/stdcpp-time-point.stderr b/cpp-overload-test/examples/stdcpp-time-point.stderr new file mode 100644 index 0000000..cbe9db7 --- /dev/null +++ b/cpp-overload-test/examples/stdcpp-time-point.stderr @@ -0,0 +1,18 @@ +error[E0119]: conflicting implementations of trait `NewArgs` for type `(&StdTimePoint,)` + --> cpp-overload-test/examples/stdcpp-time-point.rs:35:1 + | +35 | / overload! { +36 | | impl StdTimePoint { +37 | | +38 | | /// Copy constructor +... | +48 | | } + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `(&StdTimePoint,)` + | + = note: this error originates in the macro `overload` (in Nightly builds, run with -Z macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0119`. +error: could not compile `cpp-overload-test` (example "stdcpp-time-point") due to 1 previous error diff --git a/cpp-overload-test/src/lib.rs b/cpp-overload-test/src/lib.rs index f6b1a92..98274d5 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 time_point; pub mod unordered_map; pub mod unordered_multiset; } diff --git a/cpp-overload-test/src/stdcpp/time_point.rs b/cpp-overload-test/src/stdcpp/time_point.rs new file mode 100644 index 0000000..34c1a4f --- /dev/null +++ b/cpp-overload-test/src/stdcpp/time_point.rs @@ -0,0 +1,511 @@ +//! C++ overloads tests for [std::chrono::time_point][time_point] +//! +//! This file tests four constructors that can be represented in Rust. +//! +//! This test focuses on selected overloads rather than reproducing +//! the complete c++ API. Overloaded operators, assignment operations, +//! and other excluded are outside it's scope. +//! +//! [time_point]: https://en.cppreference.com/cpp/chrono/time_point + +#![allow(unused_braces, reason = "Generated by overload! macro")] + +use cpp::cpp; +use splat_overload::overload; +use std::ffi::{c_longlong, c_void}; +use std::marker::PhantomData; + +cpp! {{ + #include + +// ### Limitations +// +// C++ time points use clock and duration types. +// This test uses fixed types instead of supporting all possible types. + + using TestClock = std::chrono::system_clock; + using TestMillisecondsDuration = std::chrono::milliseconds; + using TestSecondsDuration = std::chrono::seconds; + + using TestMillisecondsTimePoint = + std::chrono::time_point; + + using TestSecondsTimePoint = + std::chrono::time_point; +}} + +/// A wrapper around a C++ time point with millisecond precision. +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdMillisecondsTimePoint(*mut c_void, PhantomData<()>); + +/// A Wrapper around a C++ time point with second precision. +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdSecondsTimePoint(*mut c_void, PhantomData<()>); + +/// A wrapper around a c++ duration with millisecond precision +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdMillisecondsDuration(*mut c_void, PhantomData<()>); + +/// A wrapper around a c++ duration with second precision. +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +pub struct StdSecondsDuration(*mut c_void, PhantomData<()>); + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +#[cfg_attr(test, allow(dead_code))] +impl StdMillisecondsDuration { + /// Construct a millisecond duration from its tick count. + fn from_count(milliseconds: c_longlong) -> Self { + let duration = unsafe { + cpp!([milliseconds as "long long"] -> *mut c_void as + "TestMillisecondsDuration*" { + return new TestMillisecondsDuration(milliseconds); + }) + }; + Self(duration, PhantomData) + } + + /// Return the number of milliseconds in the duration. + fn count(&self) -> c_longlong { + let duration = self.0 as *const c_void; + + unsafe { + cpp!([duration as "const TestMillisecondsDuration*"] -> c_longlong as + "long long" { + return duration->count(); + }) + } + } +} + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +impl StdSecondsDuration { + /// Return the number of seconds in the duration. + fn count(&self) -> c_longlong { + let duration = self.0 as *const c_void; + + unsafe { + cpp!([duration as "const TestSecondsDuration*"] -> c_longlong as + "long long" { + return duration->count(); + }) + } + } +} + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +impl StdSecondsTimePoint { + /// Construct a time point from seconds since the epoch. + fn from_seconds(seconds: c_longlong) -> Self { + let point = unsafe { + cpp!([seconds as "long long"] -> *mut c_void as + "TestSecondsTimePoint*" { + return new TestSecondsTimePoint( + std::chrono::seconds(seconds) + ); + }) + }; + + Self(point, PhantomData) + } + + /// Return the number of seconds since the epoch. + fn seconds_since_epoch(&self) -> c_longlong { + let point = self.0 as *const c_void; + + unsafe { + cpp!([point as "const TestSecondsTimePoint*"] -> c_longlong as + "long long" { + return point->time_since_epoch().count(); + }) + } + } +} + +#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] +impl StdMillisecondsTimePoint { + /// Construct a time point at the clock's epoch. + fn default() -> Self { + let point = unsafe { + cpp!([] -> *mut c_void as "TestMillisecondsTimePoint*" { + return new TestMillisecondsTimePoint(); + }) + }; + + Self(point, PhantomData) + } + + /// Construct a time point from milliseconds since the epoch. + fn from_duration(milliseconds: &StdMillisecondsDuration) -> Self { + let milliseconds = milliseconds.0 as *const c_void; + + let point = unsafe { + cpp!([milliseconds as "const TestMillisecondsDuration*"] -> *mut c_void as + "TestMillisecondsTimePoint*" { + return new TestMillisecondsTimePoint(*milliseconds); + + }) + }; + + Self(point, PhantomData) + } + + /// Construct a millisecond time point from second time point. + fn from_other(other: &StdSecondsTimePoint) -> Self { + let other = other.0 as *const c_void; + + let point = unsafe { + cpp!([other as "const TestSecondsTimePoint*"] -> *mut + c_void as "TestMillisecondsTimePoint*" { + return new TestMillisecondsTimePoint(*other); + + }) + }; + + Self(point, PhantomData) + } + + /// Copy milliseconds time point + fn copy_from(other: &StdMillisecondsTimePoint) -> Self { + let other = other.0 as *const c_void; + + let point = unsafe { + cpp!([other as "const TestMillisecondsTimePoint*"] + -> *mut c_void as "TestMillisecondsTimePoint*" { + return new TestMillisecondsTimePoint(*other); + }) + }; + + Self(point, PhantomData) + } + + /// Return the number of milliseconds since epoch. + fn milliseconds_since_epoch(&self) -> c_longlong { + let point = self.0 as *const c_void; + + unsafe { + cpp!([point as "const TestMillisecondsTimePoint*"] -> c_longlong as + "long long" { + return point->time_since_epoch().count(); + }) + } + } + /// Round millisecond duration + fn round_duration(duration: &StdMillisecondsDuration) -> StdSecondsDuration { + let duration = duration.0 as *const c_void; + + let result = unsafe { + cpp!([duration as "const TestMillisecondsDuration*"] -> *mut c_void as + "TestSecondsDuration*" { + return new TestSecondsDuration( + std::chrono::round(*duration) + ); + }) + }; + + StdSecondsDuration(result, PhantomData) + } + + /// Round millisecond timepoint + fn round_point(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + let point = point.0 as *const c_void; + + let result = unsafe { + cpp!([point as "const TestMillisecondsTimePoint*"] -> *mut c_void as + "TestSecondsTimePoint*" { + return new TestSecondsTimePoint( + std::chrono::round(*point) + ); + }) + }; + + StdSecondsTimePoint(result, PhantomData) + } + + /// Round millisecond duration down to whole seconds. + fn floor_duration(duration: &StdMillisecondsDuration) -> StdSecondsDuration { + let duration = duration.0 as *const c_void; + + let result = unsafe { + cpp!([duration as "const TestMillisecondsDuration*"] -> *mut c_void as + "TestSecondsDuration*" { + return new TestSecondsDuration( + std::chrono::floor(*duration) + ); + }) + }; + + StdSecondsDuration(result, PhantomData) + } + + /// Round milisecond time point down to whole seconds. + fn floor_point(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + let point = point.0 as *const c_void; + + let result = unsafe { + cpp!([point as "const TestMillisecondsTimePoint*"] -> *mut c_void as + "TestSecondsTimePoint*" { + return new TestSecondsTimePoint( + std::chrono::floor(*point) + ); + }) + }; + + StdSecondsTimePoint(result, PhantomData) + } + + /// Round millisecond duration up to whole seconds. + fn ceil_duration(duration: &StdMillisecondsDuration) -> StdSecondsDuration { + let duration = duration.0 as *const c_void; + + let result = unsafe { + cpp!([duration as "const TestMillisecondsDuration*"] -> *mut c_void as + "TestSecondsDuration*" { + return new TestSecondsDuration( + std::chrono::ceil(*duration) + ); + }) + }; + StdSecondsDuration(result, PhantomData) + } + + /// Round a millisecond time point up to whole seconsd. + fn ceil_point(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + let point = point.0 as *const c_void; + + let result = unsafe { + cpp!([point as "const TestMillisecondsTimePoint*"] -> *mut c_void as + "TestSecondsTimePoint*" { + return new TestSecondsTimePoint( + std::chrono::ceil(*point) + ); + }) + }; + + StdSecondsTimePoint(result, PhantomData) + } + + /// Convert seconds to milliseconds. + fn cast_seconds_to_milliseconds(point: &StdSecondsTimePoint) -> StdMillisecondsTimePoint { + let point = point.0 as *const c_void; + let result = unsafe { + cpp!([point as "const TestSecondsTimePoint*"] -> *mut c_void as + "TestMillisecondsTimePoint*" { + return new TestMillisecondsTimePoint( + std::chrono::time_point_cast(*point) + ); + }) + }; + StdMillisecondsTimePoint(result, PhantomData) + } + + /// Convert milliseconds to seconds + fn cast_milliseconds_to_seconds(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + let point = point.0 as *const c_void; + let result = unsafe { + cpp!([point as "const TestMillisecondsTimePoint*"] -> *mut c_void as + "TestSecondsTimePoint*" { + return new TestSecondsTimePoint( + std::chrono::time_point_cast(*point) + ); + }) + }; + + StdSecondsTimePoint(result, PhantomData) + } +} + +// ### Ergonomics +// C++ allows constructors with the same name but different arguments. +// +// Rust does not normally support constructor overloading. +// The overload! macro allows these constructors to share the name new. +overload! { + impl StdMillisecondsTimePoint { + /// Construct time point at clock's epoch. + pub fn new() -> StdMillisecondsTimePoint { + StdMillisecondsTimePoint::default() + } + + /// Construct time point from milliseconds since the epoch. + pub fn new(milliseconds: &StdMillisecondsDuration) -> StdMillisecondsTimePoint { + StdMillisecondsTimePoint::from_duration(milliseconds) + } + + /// Construct time point from seconds-resolution time point. + pub fn new(other: &StdSecondsTimePoint) -> StdMillisecondsTimePoint { + StdMillisecondsTimePoint::from_other(other) + } + + /// copy millisecond time point + pub fn new(other: &StdMillisecondsTimePoint) -> StdMillisecondsTimePoint { + StdMillisecondsTimePoint::copy_from(other) + } + } +} + +// overloading the round +// ### Limitations +// +// This test covers only conversions from milliseconds to seconds. +overload! { + impl StdMillisecondsTimePoint { + + pub fn round(duration: &StdMillisecondsDuration) -> StdSecondsDuration { + StdMillisecondsTimePoint::round_duration(duration) + } + + pub fn round(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + StdMillisecondsTimePoint::round_point(point) + } + + } +} + +// overloading the floor +// +// ### Limitations +// +// This test covers only conversions from milliseconds to seconds. +overload! { + impl StdMillisecondsTimePoint { + + pub fn floor(duration: &StdMillisecondsDuration) -> StdSecondsDuration { + StdMillisecondsTimePoint::floor_duration(duration) + } + + pub fn floor(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + StdMillisecondsTimePoint::floor_point(point) + } + } +} + +// overloading the ceil +// +// ### Limitations +// +// This test covers only conversions from milliseconds to seconds. +overload! { + impl StdMillisecondsTimePoint { + + pub fn ceil(duration: &StdMillisecondsDuration) -> StdSecondsDuration { + StdMillisecondsTimePoint::ceil_duration(duration) + } + + pub fn ceil(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + StdMillisecondsTimePoint::ceil_point(point) + } + } +} + +// overloading the time_point_cast +// +// ### Limitations +// +// This test covers only conversions between two timepoint types defined +// in this test. +overload! { + impl StdMillisecondsTimePoint { + + pub fn time_point_cast(point: &StdMillisecondsTimePoint) -> StdSecondsTimePoint { + StdMillisecondsTimePoint::cast_milliseconds_to_seconds(point) + } + + pub fn time_point_cast(point: &StdSecondsTimePoint) -> StdMillisecondsTimePoint { + StdMillisecondsTimePoint::cast_seconds_to_milliseconds(point) + } + } +} + +#[test] +fn test_time_point_constructors() { + // Test the default constructor. + let epoch = StdMillisecondsTimePoint::new(); + assert_eq!(epoch.milliseconds_since_epoch(), 0); + + // Test the duration constructor. + let duration = StdMillisecondsDuration::from_count(1500); + let milliseconds = StdMillisecondsTimePoint::new(&duration); + assert_eq!(milliseconds.milliseconds_since_epoch(), 1500); + + // Test the converting constructor. + let seconds = StdSecondsTimePoint::from_seconds(2); + let converted = StdMillisecondsTimePoint::new(&seconds); + assert_eq!(converted.milliseconds_since_epoch(), 2000); + + // Test the copy constructor + let original = StdMillisecondsTimePoint::new(&duration); + let copied = StdMillisecondsTimePoint::new(&original); + assert_eq!( + copied.milliseconds_since_epoch(), + original.milliseconds_since_epoch() + ); +} + +#[test] +fn test_round_overloads() { + //Duration overload + let duration = StdMillisecondsDuration::from_count(1500); + let rounded_duration = StdMillisecondsTimePoint::round(&duration); + assert_eq!(rounded_duration.count(), 2); + + // Time point overload. + let point = StdMillisecondsTimePoint::new(&duration); + let rounded_point = StdMillisecondsTimePoint::round(&point); + assert_eq!(rounded_point.seconds_since_epoch(), 2); + + // nearest value + let duration = StdMillisecondsDuration::from_count(2500); + let rounded_duration = StdMillisecondsTimePoint::round(&duration); + assert_eq!(rounded_duration.count(), 2); +} + +#[test] +fn test_floor_overloads() { + // Duration overload + let duration = StdMillisecondsDuration::from_count(1500); + let floored_duration = StdMillisecondsTimePoint::floor(&duration); + assert_eq!(floored_duration.count(), 1); + + // Time point overload + let point = StdMillisecondsTimePoint::new(&duration); + let floored_point = StdMillisecondsTimePoint::floor(&point); + assert_eq!(floored_point.seconds_since_epoch(), 1); + + // floor negative values + let duration = StdMillisecondsDuration::from_count(-1500); + let floored_duration = StdMillisecondsTimePoint::floor(&duration); + assert_eq!(floored_duration.count(), -2); +} + +#[test] +fn test_ceil_overloads() { + // Duration overload. + let duration = StdMillisecondsDuration::from_count(1500); + let ceiled_duration = StdMillisecondsTimePoint::ceil(&duration); + assert_eq!(ceiled_duration.count(), 2); + + // Time point overload. + let point = StdMillisecondsTimePoint::new(&duration); + let ceiled_point = StdMillisecondsTimePoint::ceil(&point); + assert_eq!(ceiled_point.seconds_since_epoch(), 2); + + // Ceil the values + let duration = StdMillisecondsDuration::from_count(-1500); + let ceiled_duration = StdMillisecondsTimePoint::ceil(&duration); + assert_eq!(ceiled_duration.count(), -1); +} + +#[test] +fn test_time_point_cast_overloads() { + // Milliseconds to seconds. + let duration = StdMillisecondsDuration::from_count(5756); + let milliseconds = StdMillisecondsTimePoint::new(&duration); + + let seconds = StdMillisecondsTimePoint::time_point_cast(&milliseconds); + assert_eq!(seconds.seconds_since_epoch(), 5); + + //seconds to milliseconds + let seconds = StdSecondsTimePoint::from_seconds(4); + let milliseconds = StdMillisecondsTimePoint::time_point_cast(&seconds); + assert_eq!(milliseconds.milliseconds_since_epoch(), 4000); +} diff --git a/cpp-overloads-in-rust.md b/cpp-overloads-in-rust.md index b9b321b..7c5d0b4 100644 --- a/cpp-overloads-in-rust.md +++ b/cpp-overloads-in-rust.md @@ -30,6 +30,7 @@ See the Limitations section below the table for more details. | [hash] | ✅ | ✅ | | | | [unordered_map] | ✅ | 🌗⚙️[^5] | 🌗⚙️ | | | [unordered_multiset] | ✅ | ✅⚙️[^3] | ✅ | ?[^4] | +| [time_point] | ✅ | ✅ | | ✅ | | TODO | ? | ? | ? | ? | Constructors: Create an instance of the class, called as `Class::Class(...)`
@@ -39,6 +40,7 @@ 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 +[time_point]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/time_point.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