Home > other >  How do you resolve ambiguity between template operators?
How do you resolve ambiguity between template operators?

Time:06-13

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?

  1. If the result is complex<float>, then precision bits of double are lost and large values are clamped to INF.
  2. 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

  • Related