I've looked far and wide and everyone seems to have a slightly different issue than me.
For simplicity say I have a template struct Complex<X>
and I want it to have overloads for real values and at least other Complex. As a rule, operations between double
or Complex<double>
and Complex<float>
(on either side) should return Complex<double>
. I'm currently using deduction guides that work quite well for this, but other options are std::common_type_t<X,Y>
, decltype(std::declval<X>() std::declval<Y>())
, etc.
(1) `auto operator (X const&)`
(2) `friend auto operator (X const&, Complex<X> const&)`
(2) `template<class Y> auto operator (Y const&)`
(3) `template<class Y> auto operator (Complex<Y> const&)`
(4) `template<class Y> friend auto operator (Y const&, Complex<X> const&)`
Here's the problem. If I write (1-2), then Complex<float>
sees doubles as floats. If I make that (2-3), then apparently adding Complex<double>
is ambiguous between (2,3,4). Non-template operators wouldn't be ambiguous, but please assume there are too many template arguments to name.
Next I thought that the CV/references were to blame, but making (1-2) operators of X
changed nothing. This appears to be opposite the behavior of auto x
which won't be a reference.
I tried adding assertions like static_assert(std::is_arithmetic_v<Y>)
to (1-2) but they don't participate.
CodePudding user response:
After trying static assertions that didn't help from the function body, I thought before I move on I should try enabling/disabling functions another way. I remember this approach not working correctly in the past, so I didn't try it until much too late.
template<class Y>
auto operator (Complex<X> const& x, Complex<Y> const& y)
-> std::enable_if<std::is_arithmetic_v<Y>,
Complex<decltype(std::declval<X>(), std::declval<Y>())>> {/*...*/}
template<class Y>
auto operator (Complex<X> const& x, Y const& y)
-> std::enable_if<std::is_arithmetic_v<Y>,
Complex<decltype(std::declval<X>(), std::declval<Y>())>> {/*...*/}
template<class Y>
friend auto operator (Y const& y, Complex<X> const& x)
-> std::enable_if<std::is_arithmetic_v<Y>,
Complex<decltype(y, std::declval<X>())>> {/*...*/}
This isn't the code I'm using, I've paraphrased everything. Please let me know if I made a mistake somewhere.
This table shows the sum type of the row and column correctly. If either field type has double precision then so does the result; the same goes if either type is complex.
add f d Complex<f> Complex<d>
f f d Complex<f> Complex<d>
d d d Complex<d> Complex<d>
Complex<f> Complex<f> Complex<d> Complex<f> Complex<d>
Complex<d> Complex<d> Complex<d> Complex<d> Complex<d>
As an aside, if you define assignment operators like operator =
, then Complex<X>
only goes on the left (no friend functions) and only returns Complex<X>&
(type is fixed.) It should probably accept the same types as operator
or use operator
directly, so it only rounds at assignment/conversion.
template<class Y>
auto operator =(Y const& y)
-> std::enable_if<std::is_arithmetic_v<Y>,
Complex>& { /*...*/ }
CodePudding user response:
I assume that you don't want to use std::complex
for some reason. Now you have to decide the type of complex<float> double
; is it going to loose some data?
- If the result is
complex<float>
, then precision bits ofdouble
are lost and large values are clamped toINF
. - If the result is
double
, then the imaginary part is gone.
Thus, the best of both worlds would be complex<double>
. A closer looks reveals that your problem boils down to conversion/promotion to/from numeric types. You are going to need conversion constructors:
#include <concept>
template<std::floating_point numeric>
class complex{
public:
constexpr complex(numeric const r=0,numeric const i=0);
template<std::floating_point othern>
requires std::constructible_from<numeric,othern>
explicit(!std::convertible_to<othern, numeric>)
constexpr complex(complex<othern> const&);
//...
Now you can simply define:
//continue class declaration:
friend auto const& operator(complex rgt, complex const& lft)
{return rgt =lft;};
complex const& operator =(complex const&);
//...
This approach hits two birds with one stone. For completness I would define one conversion operator too:
//still inside class complex:
explicit constexper operator numeric() const
{return this->real();};
//...
The explicit
specifiers - in declaration of conversion constructors and operator - conducts correct deduction and required implicit. conversions