How do we specify a trait that includes all operator overloading combinations for references and values? Essentially, I've a situation where I'd like to define an algebra that includes operations like Add
, Mul
, etc. for generic types. Depending on the situation, this may include adding a reference to a reference, value to a reference, reference to a value, and value to a value, so we have four different situations. As an example of this, we have the code:
// Create some random type that we want to represent as a Real
#[derive(Debug,Clone)]
struct Foo <Real> {
x : Real,
y : Real,
}
// Add the algebra for Foo
impl <Real> std::ops::Add <&'_ Foo<Real>> for &'_ Foo <Real>
where
for <'a> &'a Real : std::ops::Add<&'a Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : &'_ Foo <Real>) -> Self::Output {
Foo {
x : &self.x + &other.x,
y : &self.y + &other.y,
}
}
}
impl <Real> std::ops::Add <Foo<Real>> for &'_ Foo <Real>
where
for <'a> &'a Real : std::ops::Add<Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : Foo <Real>) -> Self::Output {
Foo {
x : &self.x + other.x,
y : &self.y + other.y,
}
}
}
impl <Real> std::ops::Add <&'_ Foo<Real>> for Foo <Real>
where
for <'a> Real : std::ops::Add<&'a Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : &'_ Foo <Real>) -> Self::Output {
Foo {
x : self.x + &other.x,
y : self.y + &other.y,
}
}
}
impl <Real> std::ops::Add <Foo<Real>> for Foo <Real>
where
Real : std::ops::Add<Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : Foo <Real>) -> Self::Output {
Foo {
x : self.x + other.x,
y : self.y + other.y,
}
}
}
// Compute a function on a slice of Reals
fn foo <Real> (x : &[Real]) -> Real
where
for <'a> &'a Real :
std::ops::Add<&'a Real,Output=Real> +
std::ops::Add<Real,Output=Real> +
Clone,
for <'a> Real :
std::ops::Add<&'a Real,Output=Real> +
std::ops::Add<Real,Output=Real> +
Clone,
Real : Clone
{
&x[0]+&x[1]+&x[2]
}
// Run foo on two different types
fn main() {
let x = vec![1.2,2.3,3.4];
let _x = foo::<f64>(&x);
println!("{:?}",_x);
let y : Vec <Foo<f64>>= x.into_iter().map(|z|Foo{x:z,y:z+1.0}).collect();
let _y = foo::<Foo<f64>>(&y);
println!("{:?}",_y);
}
Here, the function foo
can operate generically on either Foo<f64>
or f64
as well as other types. That said, the trait bound is already getting large and we've only implemented Add
. Further, foo
requires a trait bound on both Real
as well as for <'a> &'a Real
, the type and its reference. I'd like to encapsulate this into a single trait if possible. If not possible, I'd still like a single trait for the type and its reference.
That said, I'm not entirely sure how to define this type. I'd like something kind of like
trait MyFloat :
std::ops::Add <REFSELF,Output = NOREFSELF> +
std::ops::Add <NOREFSELF,Output = NOREFSELF>
where
Self : std::marker::Sized,
{}
impl <T> MyFloat for T where T:
std::ops::Add <REFSELF,Output = NOREFSELF> +
std::ops::Add <NOREFSELF,Output = NOREFSELF>
{}
However, I don't know how to properly implement it. The problem is that Self
may be a reference type, &T
, or it may be a value type, T
. I always need the output type to be the non-reference version and I need the different combinations of reference or not for the right hand side. That said, I don't know of any operations on types to strip the unwanted &
. Or, candidly, if this is the best approach at all.
As a final comment, I don't believe the num
crate solves this issue. Thought num
provides traits like Float
, it appears to me that they rely on the Copy
trait as opposed to grinding through all combinations of reference vs value. Though, certainly, I may be mistaken.
Thanks for the help!