0

How would one add a template constructor to the class so that copy initialization from complex to complex is performed explicitly and without ambiguity? Is there a solution that is compiler and C++ version/standard agnostic? Is there an approach that only requires definition of a constructor without an additional operator overload?

I included the template copy constructor and operator overload (last two methods defined in the class) but the compiler gives me the following message.

Compilation error
main.cpp: In function ‘void testTemplateConstructor()’:
main.cpp:74:27: error: conversion from ‘complex<float>’ to ‘complex<double>’ is ambiguous
      complex<double> cd = cf;
                           ^~
main.cpp:35:5: note: candidate: complex<T>::operator complex<X>() [with X = double; T = float]
     operator complex<X>(){
     ^~~~~~~~
main.cpp:29:5: note: candidate: complex<T>::complex(complex<X>&) [with X = float; T = double]
     complex(complex<X>& arg) {
     ^~~~~~~

This is the test case being utilized.

void testTemplateConstructor() {
     complex<float> cf{1.0f, 2.0f};
     complex<double> cd = cf;
    
     Assert(cf.real()==cd.real(), "Real parts are different.");
     Assert(cf.imag()==cd.imag(), "Imaginary parts are different.");
}
template <typename T> class complex{    
    
    private:
    typedef complex<T> complexi;
    T real_;
    T imag_;
    
    public:
    complex(){
        real_ = 0;
        imag_ = 0;
    }
    complex(T a, T b){
        real_ = a;
        imag_ = b;
    }
    complex(T a){
        real_ = a;
    }
    complex(complex<T>& comp){
        real_ = comp.real_;
        imag_ = comp.imag_;
    }
  template <typename X>
    complex(complex<X>& arg) { 
        real_ = arg.real_;
        imag_ = arg.imag_;
    }
   
    template <typename X>                  
    operator complex<X>(){
        return complex<T>();
    }
};
Darnoc Eloc
  • 75
  • 11

2 Answers2

1

so that copy initialization from complex to complex is performed explicitly and without ambiguity? Is there a solution that is compiler and C++ version/standard agnostic?

Yes, in this particular example, you can add a low level const to the parameter of the ctors.

template <typename T> class complex{    
    
    public:
    typedef complex<T> complexi;
    T real_;
    T imag_;
    
    public:
    complex(){
        real_ = 0;
        imag_ = 0;
    }
    complex(T a, T b){
        real_ = a;
        imag_ = b;
    }
    complex(T a){
        real_ = a;
    }
    complex(const complex<T>& comp){
        std::cout<<"nornal version";;
        real_ = comp.real_;
        imag_ = comp.imag_;
    }
   template <typename X>
     complex(const complex<X>& arg) { 
        std::cout<<"template version";;
        real_ = arg.real_;
        imag_ = arg.imag_;
    }
   
   
};
void testTemplateConstructor() {
     complex<float> cf{1.0f, 2.0f};
     complex<double> cd = cf;
    
    
}

Demo

Jason
  • 36,170
  • 5
  • 26
  • 60
  • Do you even need both of them? Aren't they doing the same thing? – Goswin von Brederlow Jun 11 '22 at 12:10
  • @GoswinvonBrederlow OP didn't mention in the question that completely removing one of them is also an option. Otherwise, doing that is an obvious way of removing the ambiguity. Anyway i have added that point at the end of my answer though i do think OP is already aware of that. – Jason Jun 11 '22 at 12:11
  • I just wonder if there is any case where you need one of them and the other won't work and vice versa. Is there any case where you would need both? – Goswin von Brederlow Jun 11 '22 at 12:15
  • @GoswinvonBrederlow Most probably not. But C++ never ceases to surprise. So there is possibility of those cases. – Jason Jun 11 '22 at 12:24
  • I had already tried adding explicit, but still get this error when adding explicit to both and an even longer error message when only adding to the copy constructor. main.cpp:74:27: error: conversion from ‘complex’ to non-scalar type ‘complex’ requested complex cd = cf; – Darnoc Eloc Jun 11 '22 at 12:28
  • @DarnocEloc Don't add `explicit` to both the ctor and the overloaded `operator` at the same time. Just add to any one of them at a time as shown in my answer. See [demo](https://onlinegdb.com/Lidcq8P68) – Jason Jun 11 '22 at 12:30
  • @DarnocEloc See the [working demo1](https://onlinegdb.com/51cle7WJXl) and [working demo2](https://onlinegdb.com/Tt0aJbv-y). The same links are also given/added in the answer. – Jason Jun 11 '22 at 12:33
  • As I stated previously, neither of these approaches work for me, what compiler and standard are you using/referencing? – Darnoc Eloc Jun 11 '22 at 12:40
  • @DarnocEloc They work in GCC & Clang with C++17 last i checked. That is why i gave the demo links so that people can see it for themselves that they work. – Jason Jun 11 '22 at 12:43
  • It is a compiler issue, according to godbolt.org, it works with x86-64 gcc 11.1 and beyond, the latest clang version x86-64 clang 14.0.0 did not work. Clang gives message :43:22: error: no viable constructor copying variable of type 'complex' complex cd = cf;. – Darnoc Eloc Jun 11 '22 at 14:26
  • How were scenarios such as this dealt with in earlier versions? – Darnoc Eloc Jun 12 '22 at 00:41
  • 1
    It works in all compilers and all C++ versions if you add a low level const in the templated constructor. See [working demo with const](https://godbolt.org/z/j1szPGEv3). Note in the above demo link i have added a `const` and highlighted it with a comment so that it is easy to spot. I have added the same in my answer. Check out my updated answer. – Jason Jun 12 '22 at 04:03
  • Basic error on my part not adding const to copy constructors. For some reason still not working in the original **stepik** compiler I was working in. – Darnoc Eloc Jun 13 '22 at 12:08
  • Can you comment on the specific effect of the const keyword in this case? – Darnoc Eloc Jun 14 '22 at 11:20
  • 1
    @DarnocEloc By adding `const` the parameter of the ctors become *lvlaue reference to `const` object* meaning they can now be bound to temporaries of the respective types[demo](https://wandbox.org/permlink/mCbHVUXRI5uc7v3Y). Before when there was no `const`, the parameters were simple *lvalue reference to non-const object* which means before they could not be bound to temporaries and you'll get error saying the same. [demo](https://wandbox.org/permlink/57oRl8DjM2t5U5Gn). Also, note that you should remove the `operator complex()` otherwise you can have infinite recursion. – Jason Jun 14 '22 at 11:33
  • But a move constructor could be used to bind the temporaries to r-value references. – Darnoc Eloc Jun 14 '22 at 17:05
  • @DarnocEloc Your class template `Complex` doesn't have any move constructor because you've defined a copy constructor for your class. So the move ctor cannot be used in your case since there is no move ctor available for your case. – Jason Jun 15 '22 at 03:55
  • Hypothetically of course, I said a move ctor could be used. – Darnoc Eloc Jun 15 '22 at 16:42
  • @DarnocEloc Yes then a move ctor can be used. – Jason Jun 15 '22 at 16:48
0

This is the solution I was looking for, this works with the C++11 standard and compiler versions as old as x86_64 gcc 4.7.1 and clang 3.4.1. The only difference apart from the use of static_cast is the use of getters.

using namespace std;

template <typename T> class complex{    
    
    public:
    typedef complex<T> complexi;
    T real_;
    T imag_;
    
    public:
    complex(){
        real_ = 0;
        imag_ = 0;
    }
    complex(T a, T b){
        real_ = a;
        imag_ = b;
    }
    complex(T a){
        real_ = a;
    }
    complex(const complex<T>& comp){
        real_ = comp.real_;
        imag_ = comp.imag_;
    }

    template <typename X>
    complex(const complex<X>& rhs){
        real_ = static_cast<T>(rhs.real());
        imag_ = static_cast<T>(rhs.imag());
    }

    T real() const {
        return real_;
    }
    T imag() const {
        return imag_;
    }

  
};
Darnoc Eloc
  • 75
  • 11