TL;DR version:
For managed code, use %
for a pass by reference parameter, not &
You diagnosis is not completely correct. Boxing has nothing to do with your problem. But reference types do, in a way.
You were really close when you said that "I found that the error comes from the operand being a reference type". Well, the operand is a value type not a reference type. But the error occurs when the operand is stored inside a reference type, because then it's inside the garbage-collected heap (where all instances of reference types are placed). This goes for arrays as well as your own objects which contain a member of value type.
The danger is that when the garbage collector runs, it can move items around on the gc heap. And this breaks native pointers (*
) and references (&
), because they store the address and expect it to stay the same forever. To handle this problem, C++/CLI provides tracking pointers (^
) and tracking references (%
) which work together with the garbage collector to do two things:
- make sure the enclosing object isn't freed while you're using it
- find the new address if the garbage collector moves the enclosing object
For use from C++/CLI, you can make operator+
a non-member, just like normal C++.
value class c_Location
{
public:
double x, y, z;
c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}
c_Location% operator+= (const c_Location% i_locValue)
{
x += i_locValue.x;
y += i_locValue.y;
z += i_locValue.z;
return *this;
}
};
c_Location operator+ (c_Location left, const c_Location% right)
{
return left += right;
}
The drawback is that C# won't use non-members, for compatibility with C#, write it like a non-member operator (with two explicit operands) but make it a public static member.
value class c_Location
{
public:
double x, y, z;
c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}
c_Location% operator+= (const c_Location% i_locValue)
{
x += i_locValue.x;
y += i_locValue.y;
z += i_locValue.z;
return *this;
}
static c_Location operator+ (c_Location left, const c_Location% right)
{
return left += right;
}
};
There's no reason to worry about this for operator+=
since C# doesn't recognize that anyway, it will use operator+
and assign the result back to the original object.
For primitive types like double
or int
, you may find that you need to use %
also, but only if you need a reference to an instance of that primitive type is stored inside a managed object:
double d;
array<double>^ a = gcnew darray<double>(5);
double& native_ref = d; // ok, d is stored on stack and cannot move
double& native_ref2 = a[0]; // error, a[0] is in the managed heap, you MUST coordinate with the garbage collector
double% tracking_ref = d; // ok, tracking references with with variables that don't move, too
double% tracking_ref2 = a[0]; // ok, now you and the garbage collector are working together