A reference to separate pairs of values

This is a discussion on A reference to separate pairs of values within the C++ Programming forums, part of the General Programming Boards category; C++ has a complex class which has overloaded the basic arithmetic operators. That's nice but there are some issues with ...

  1. #1
    Registered User
    Join Date
    Apr 2007
    Posts
    129

    A reference to separate pairs of values

    C++ has a complex class which has overloaded the basic arithmetic operators.
    That's nice but there are some issues with regards to assignment of complex values.

    Suppose you have another class that implements a complex array internally as two separate arrays of real and imaginary parts.

    Code:
    class carr {
    double *rval ;
    double *ival ;
    unsigned len ;
    ....
    public:
    }
    I wrote part of an iterator for a class like this. But I want my iterators * operator to return a reference to a complex number. While I can easily create such an iterator I can not assign to the * operator. In fact there is no way to return a complex reference to an element of this 'array' that I can see.

    For example wouldn't it be nice if I could,
    Code:
    carr  c1(100) ;
    c1[25] = std::complex<double>(1.0, -1.0) ;
    There is no [] operator that I can construct that would support these semantics as far as I can tell. I think if I had a single flat double array of twice the size, it could be done with a cast, but otherwise am I out of luck? Do I require a separate set value function?

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    21,589
    Suppose you have another class that implements a complex array internally as two separate arrays of real and imaginary parts.
    Why not simply implement the internal complex array as an array of std::complex<double>?
    C + C++ Compiler: MinGW port of GCC
    Version Control System: Bazaar

    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    You have to invent some kind of reference object which encapsulates the action of setting the separate real and imaginary parts.

  4. #4
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    To clarify what I was saying.

    I would describe this representation of complex number as "planar," as in, having a plane of real components and a plane of imaginary components. You want to interface this with a more "traditional" complex class, where real and imaginary are kept together. Call this traditional complex class "trad_complex." Then, you could:

    Code:
    class complex_ref
    {
    public:
        complex_ref(double &real_p, double &imag_p)
          : real(real_p), imag(imag_p)
        {}
    
        complex_ref &operator=(const complex_ref &complex_p)
        {
            real = complex_p.real;
            imag = complex_p.imag;
            return *this; // duh -- I forgot this initially
        }
    
        complex_ref &operator=(const trad_complex &trad_p)
        {
            real = trad_p.real;
            imag = trad_p.imag;
            return *this; // duh -- I forgot this initially
        }
    
        // Allow conversion to trad_complex
        operator trad_complex() const
        {
            return trad_complex(real, imag);
        }
    
        // Any other relevant operations...
    
        double &real;
        double &imag;
    };
    When this type is constructed, it holds references to the actual real and imaginary parts. These parts could be located anywhere -- such as in a planar storage format. In practice, you might make the constructor of this class private, and make the complex iterator a friend, so that the only code which can construct complex_ref objects is the iterator itself (to prevent abuse of the class).

    The real and imag parts are accessed as if they were contiguous members of the class -- however, they're really just references to the actual data.

    Allowing conversion to trad_complex allows you to use the (presumably rich) set of features of the traditional complex class.
    Last edited by brewbuck; 04-03-2008 at 11:43 AM.

  5. #5
    The larch
    Join Date
    May 2006
    Posts
    3,573
    How would class carr be different from std::vector<std::complex<double> >?
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  6. #6
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    Quote Originally Posted by anon View Post
    How would class carr be different from std::vector<std::complex<double> >?
    Look at the class -- all the real parts are in one array, all the imaginary parts in another array. A std::complex<double> holds both the real and imaginary parts "right next to each other."

    It's like the difference between flat color and planar color, in graphics.

  7. #7
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Yes, but isn't that an implementation detail? For example considering the example usage:
    Code:
    carr  c1(100) ;
    c1[25] = std::complex<double>(1.0, -1.0) ;
    That would work, if carr stood for std::vector<std::complex<double> > and no new class would be necessary.

    Is there something I don't see, but I can't see why one would want to go through all the hoops if the implementation decisions make it hard to implement desired behaviour.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  8. #8
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    Quote Originally Posted by anon View Post
    Yes, but isn't that an implementation detail? For example considering the example usage:
    Code:
    carr  c1(100) ;
    c1[25] = std::complex<double>(1.0, -1.0) ;
    That would work, if carr stood for std::vector<std::complex<double> > and no new class would be necessary.

    Is there something I don't see, but I can't see why one would want to go through all the hoops if the implementation decisions make it hard to implement desired behaviour.
    Without knowing the application I can't say, but planar representations can have advantages in certain situations.

    The impression I got was that this "planar complex array" was something out of the OP's control, so I just tried to come up with something to turn it into a sane interface.

  9. #9
    Registered User
    Join Date
    Apr 2007
    Posts
    129
    Quote Originally Posted by brewbuck View Post
    You have to invent some kind of reference object which encapsulates the action of setting the separate real and imaginary parts.
    OK that's interesting. The semantic trickery I missed is the effect of declaring

    Code:
    double &real ;
    double &imag ;
    If I then initialize that with
    Code:
    carr c1(100) ;
    
    real = *(c1.rval + k) ;
    imag = *(c1.rval + k) ;
    
    // this assigns to the k'th element of the carr array?
    real = 1.0 ;
    imag = -1.0 ;
    What you are saying is that you then wrap real and imag in a class and overload the assignment operator with the code segment you provided,
    Code:
    complex_ref &operator=(const complex_ref &complex_p)
        {
            real = complex_p.real;
            imag = complex_p.imag;
        }
    This allows something like
    Code:
    complex_ref   cr(c1.rval + 25, c1.ival + 25) ;
    cr = std::complex<double>(1.0,-1.0) ;
    That's pretty nifty, and a subtle point with regards to the meaning of a 'reference' value, which is a kind of pointer in disguise I suppose. I'm actually a little confused by this however, so I'm not sure if I really understand this.

    By what you are saying there is a vast difference between assignment and initialization for reference values. ie
    Code:
    double x = 5.0 ;
    double y = 6.0 ;
    double &r(x) ;
    // r == 5
    r = 7 ; // x = 7 
    r = y ; // x = y = 6.0
    // how do I change r's reference to point to y?
    Is this correct?
    Edit:
    It seems to be correct based on a quick search. However reference can only be initialized once. You can't change what they are pointing to.
    Last edited by SevenThunders; 04-03-2008 at 11:48 AM.

  10. #10
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    21,589
    You cannot assign to a reference (as in assign to the reference itself), so in the given scope, r can only ever be an alias for x.
    C + C++ Compiler: MinGW port of GCC
    Version Control System: Bazaar

    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  11. #11
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    Quote Originally Posted by SevenThunders View Post
    By what you are saying there is a vast difference between assignment and initialization for reference values. ie
    Code:
    double x = 5.0 ;
    double y = 6.0 ;
    double &r(x) ;
    // r == 5
    r = 7 ; // x = 7 
    r = y ; // x = y = 6.0
    // how do I change r's reference to point to y?
    Is this correct?
    Yes, a reference can only be "seated" during construction time. Once constructed, all assignments to the reference simply assign to the underlying value, they cannot "reseat" the reference. So the references inside the complex_ref are seated in the constructor, but after that point, they act as shuttles to the actual data.

    EDIT: To further clarify all this, the intention of this complex_ref class is that the iterator's operator*() will return one of them. It builds a complex_ref from the appropriate real and imaginary parts, and returns it. The caller who uses this reference can act as if it was just a trad_complex -- it is assignable as such.
    Last edited by brewbuck; 04-03-2008 at 11:46 AM.

  12. #12
    Registered User
    Join Date
    Apr 2007
    Posts
    129
    Quote Originally Posted by brewbuck View Post
    Without knowing the application I can't say, but planar representations can have advantages in certain situations.

    The impression I got was that this "planar complex array" was something out of the OP's control, so I just tried to come up with something to turn it into a sane interface.
    Basically I've got a thousand lines of dense C code plus an entire interface to Matlab that uses a format with separate real and imaginary arrays. The purpose of my class is to provide a kind of interface to another set of libraries that do things properly. Flat arrays for multidimensional real and complex tensors and matrices.

    Here is another odd thought along the lines of Brewbuck's solution. What would happen if I declared the type:

    typedef std::complex<double &> complexref ;

    and then provided overloaded assignment operators for going back and forth between the two types
    Code:
    std::complex<double>  // and
    std::complex<double &>
    Would this work?

  13. #13
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    21,589
    Would this work?
    No, since you cannot have a container of references.
    C + C++ Compiler: MinGW port of GCC
    Version Control System: Bazaar

    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  14. #14
    Registered User
    Join Date
    Apr 2007
    Posts
    129
    Quote Originally Posted by laserlight View Post
    No, since you cannot have a container of references.
    It does compile however, but I've not tried to use it. What would the problem be? It seems like most of the natural complex op.s would work.

    Edit: Nope you are right it won't let me construct an array of references.
    Last edited by SevenThunders; 04-03-2008 at 12:23 PM.

  15. #15
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    Quote Originally Posted by SevenThunders View Post
    It does compile however, but I've not tried to use it. What would the problem be? It seems like most of the natural complex op.s would work.
    The problem is that when a reference is created it MUST be seated to another variable. In the following code, where exactly does that reference get seated?

    Code:
    std::complex<double &> val;
    std::complex will attempt to default-construct the real and imaginary parts -- but since these parts are of type "double &" there is no such default constructor.

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Getting an error with OpenGL: collect2: ld returned 1 exit status
    By Lorgon Jortle in forum C++ Programming
    Replies: 6
    Last Post: 05-08-2009, 08:18 PM
  2. problem creating .so
    By k0k33 in forum C Programming
    Replies: 7
    Last Post: 04-27-2009, 04:41 AM
  3. Replies: 7
    Last Post: 10-09-2007, 02:37 PM
  4. Problem with Makefile
    By pinkprincess in forum C Programming
    Replies: 3
    Last Post: 06-24-2007, 09:02 AM
  5. C OpenGL Compiler Error?
    By Matt3000 in forum C Programming
    Replies: 12
    Last Post: 07-07-2006, 04:42 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21