0

TLDR; I want to use operators defined for my class with primitive data types that can be converted into my class without typecasting/initializing new variables. i.e.,

mycomplex x = 5 , y ;
y = x + 3 ;

As a side project, I am developing a complex number class. For obvious reasons, any primitive numeric data type can be interpreted as a complex number. Therefore, in any operation involving a primitive type and a complex, I would like to allow the primitive type to convert to a complex and continue the operation using my defined operations involving two complex numbers.

Each primitive type can be overloaded on each operator, but order matters, and with 4 basic operators and a few higher math functions it is quickly going to turn into alot of code. So the question is, how to write my class such that a primitive type will "cast" to a complex and perform normal complex operations.

This code compiles if the second to last line of main is commented out. Getting that line to evaluate to a mycomplex with cout of 8+2i is the goal here.

#include <iostream>

template <class T>
class mycomplex{
    private:
        T real ; 
        T imag ; 
    public:
        mycomplex( const mycomplex<T> & x ) ;
        mycomplex( const T & realx , const T & imagx ) ; 
        mycomplex( const T & x ) ; 
        mycomplex( ) ;

        template <class U>
        friend std::ostream & operator << ( std::ostream & os , const mycomplex<U> & x ) ;

        template <class U>
        friend mycomplex<U> operator + ( const mycomplex<U> & lhs , const mycomplex<U> & rhs ) ;
} ;

int main( int argc , char * argv[] ){
    mycomplex<float> x = 5 , y( 3 , 2 ) ; 
    mycomplex<float> z = y + x ; 
    std::cout << x << '\n' << y << '\n' << z << std::endl ;
    z = 5 + y ; 
    return 0 ;
}

template <class T>
mycomplex<T>::mycomplex( const mycomplex<T> & x ){
    real = x.real ;
    imag = x.imag ; 
}
template <class T>
mycomplex<T>::mycomplex( const T & realx , const T & imagx ){
    real = realx ; 
    imag = imagx ; 
}
template <class T>
mycomplex<T>::mycomplex( const T & x ){
    real = x ; 
    imag = 0 ;
}
template <class T>
mycomplex<T>::mycomplex( ){
    real = 0 ; 
    imag = 0 ; 
}
template <class T>
std::ostream & operator << ( std::ostream & os , const mycomplex<T> & x ){
    os << x.real ; 
    if( x.imag >= 0 ) 
        os << "+" ;
    os << x.imag << "i" ;
    return os ;
}
template <class T>
mycomplex<T> operator + ( const mycomplex<T> & lhs , const mycomplex<T> & rhs ){
    mycomplex<T> ans ;
    ans.real = lhs.real + rhs.real ; 
    ans.imag = lhs.imag + rhs.imag ; 
    return ans ; 
}

I looked at this answer and implemented that as you can see above, but that doesn't let this compile though it is handy for when you declare variables. I also looked at this answer and added a type-cast to the template type, but that causes my class to go to the template, which is exactly the opposite of the goal. It allows the above code to compile but any mycomplex + T gives a mycomplex without an imaginary part, which is not correct.

Ethan
  • 49
  • 2
  • 11
  • Hi, if I manage to get you working code that uses std::enable_if would you be interested in that? I ask because enable_if is considered advanced and very ugly. – NoSenseEtAl Nov 09 '18 at 21:56

2 Answers2

1

You can use std::enable_if but it is very very ugly.

I am in a hurry so unfortunately I did not test this extensively or enabled promotions(float + complex<int> give you complex<float>). Runnable code here.

#include <iostream>
#include <type_traits>

template <class T>
class mycomplex{
    private:
        T r ; 
        T i ; 
    public:
        using Underlying = T;
        mycomplex( const mycomplex<T> & x ) ;
        mycomplex( const T & realx , const T & imagx ) ; 
        mycomplex( const T & x ) ; 
        mycomplex( ) ;

        T real() const{
            return r;
        }
        T imag() const {
            return i;
        }

        template <class U>
        friend std::ostream & operator << ( std::ostream & os , const mycomplex<U> & x ) ;
} ;

template <class T>
mycomplex<T>::mycomplex( const mycomplex<T> & x ){
    r = x.r ;
    i = x.i ; 
}
template <class T>
mycomplex<T>::mycomplex( const T & realx , const T & imagx ){
    r = realx ; 
    i = imagx ; 
}
template <class T>
mycomplex<T>::mycomplex( const T & x ){
    r = x ; 
    i = 0 ;
}
template <class T>
mycomplex<T>::mycomplex( ){
    r = 0 ; 
    i = 0 ; 
}
template <class T>
std::ostream & operator << ( std::ostream & os , const mycomplex<T> & x ){
    os << x.r ; 
    if( x.i >= 0 ) 
        os << "+" ;
    os << x.i << "i" ;
    return os ;
}

template<typename T>
struct is_mycomplex : std::false_type{
};

template<typename T>
struct is_mycomplex<mycomplex<T>> : std::true_type{
};

static_assert(!is_mycomplex<int>::value);
static_assert(is_mycomplex<mycomplex<int>>::value);

template <typename T>
auto type_helper(){
    if constexpr (is_mycomplex<T>::value){
        return typename T::Underlying{};
    } else {
    return T{};
    }
}
template <class L, class R, typename = std::enable_if_t<
    is_mycomplex<L>::value + is_mycomplex<R>::value == 2
        ||
    (is_mycomplex<L>::value + is_mycomplex<R>::value == 1
     &&
     std::is_arithmetic_v<L> + std::is_arithmetic_v<R> ==1)
    >>
auto operator + ( const L& lhs , const R& rhs ){    
    using T = decltype(type_helper<L>());
    T real = 0; 
    T imag = 0;
    if constexpr(std::is_arithmetic_v<L>) {
        real+=lhs;
    } else {
        real+=lhs.real();
        imag+=lhs.imag();
    }
    if constexpr(std::is_arithmetic_v<R>) {
        real+=rhs;
    } else {
        real+=rhs.real();
        imag+=rhs.imag();
    }

    return mycomplex<T>(real, imag);
 ; 
}

int main(){
    mycomplex<float> x = 5 , y( 3 , 2 ) ; 
    mycomplex<float> z = y + x ; 
    std::cout << x << '\n' << y << '\n' << z << std::endl ;
    z = 5.5f + y ; 
    std::cout << "\n\n" << z << std::endl ;
}
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
0

Let's look at this line:

z = 5 + y ; 

With y being mycomplex<float>, the compiler is first looking for an overload of operator+ taking an integer (the type of literal 5 is an integer) and mycomplex<float>. This doesn't exist, the only operator+ takes two mycomplex values.

Since y is already of a mycomplex type, the compiler will try to convert 5 to mycomplex<float>. This would not be possible, since you can only convert float values to mycomplex<float>.

It is clear at this point, that there is no available overload and no conversion which would make this line well-formed. Compilation terminates.

One possible solution would be to create a conversion operator from anything to mycomplex and enable it only for numeric types.

Something like that:

template<class Z, std::enable_if_t<std::is_arithmetic_v<Z>>* = nullptr>
mycomplex(Z& z);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • It is possible to implicitly convert `5` to `complex`. For example, `void f(const mycomplex&) {} void g() { f(5); }` compiles fine. The issue is with template type deduction for `operator+`. – aschepler Nov 09 '18 at 23:12