CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 8 of 8
  1. #1
    Join Date
    Jan 2004
    Posts
    29

    operator= in template derived class

    I have a template class

    Code:
    template<class T>
    class WavesetTemplate
    {
    public:
    ...
    WavesetTemplate<T>& operator=( const WavesetTemplate<T>& );
    friend WavesetTemplate<T> operator*(const WavesetTemplate<T>& lhs, const WavesetTemplate<T>& rhs);
    
    protected:
    vector<vector<T> > m_TemplateData;
    ...
    };
    and derived class:

    Code:
    class WavesetTime : public WavesetTemplate<double>
    {
    public:
    WavesetTime(){} ;
    virtual ~WavesetTime() {} ;
    WavesetTime& operator=(const WavesetTime& rhs) { WavesetTemplate<double>::operator=(rhs);return *this;} // call base operator= function
    ... //other type specific functions
    };
    This works...
    Code:
    WavesetTemplate<double> x,y,z;
    x.SetWave(0.0,flat10,0.1); //set some data
    y.SetWave(0.0,ramp,0.1); //set some data
    z=x * y; // this works
    BUT THIS
    Code:
    WavesetTime x,y,z;
    x.SetWave(0.0,flat10,0.1); //set some data
    y.SetWave(0.0,ramp,0.1); //set some data
    z=x * y; // compile error
    gives the compile error:
    binary '=' : no operator defined which takes a right-hand operand of type 'class WavesetTemplate<double>' (or there is no acceptable conversion).

    I also get the same problem when operator* is a member function, rather than here where it is non-member function.

    The only way I can get this compile error to disappear is to put operator* into the derived class WavesetTime, which rather defeats the point of templates.

    I would be very grateful if anyone could suggest a solution to this problem. Thank you.

  2. #2
    Join Date
    Apr 1999
    Posts
    27,449

    Re: operator= in template derived class

    Originally posted by JonnoA
    BUT THIS
    Code:
    WavesetTime x,y,z;
    x.SetWave(0.0,flat10,0.1); //set some data
    y.SetWave(0.0,ramp,0.1); //set some data
    z=x * y; // compile error
    gives the compile error:
    binary '=' : no operator defined which takes a right-hand operand of type 'class WavesetTemplate<double>' (or there is no acceptable conversion).
    Since operator * is defined in the WavesetTemplate<double>, you are trying to assign a WavesetTemplate<double> to a WavesetTime. There is no operator = defined that does this conversion.

    Here is the output from the Comeau compiler (after making a few corrections):
    Code:
    #include <vector>
    using namespace std;
    
    template<class T>
    class WavesetTemplate
    {
      public:
              WavesetTemplate<T>& operator=( const  WavesetTemplate<T>& );
              friend WavesetTemplate<T> operator*(const  WavesetTemplate<T>& lhs, const WavesetTemplate<T>& rhs);
    
      protected:
              vector<vector<T> > m_TemplateData;
    };
    
    class WavesetTime : public WavesetTemplate<double>
    {
      public:
           WavesetTime(){} ;
           virtual ~WavesetTime() {} ;
           WavesetTime& operator=(const WavesetTime& rhs) 
          { 
              WavesetTemplate<double>::operator=(rhs);
              return *this;
         } 
    };
    
    void foo()
    { 
      WavesetTemplate<double> x,y,z;
      z=x * y; // this works
    }
    
    void foo2()
    {
      WavesetTime x,y,z;
      z= x * y; // compile error (Line 36 from Comeau output below)
    }
    
    Output:
    =============================================
    Comeau C/C++ 4.3.3 (Aug  6 2003 15:13:37) for ONLINE_EVALUATION_BETA1
    Copyright 1988-2003 Comeau Computing.  All rights reserved.
    MODE:strict errors C++
    
    "ComeauTest.c", line 36: error: no operator "=" matches these operands
                operand types are: WavesetTime = WavesetTemplate<double>
        z= x * y; // compile error
         ^
    
    1 error detected in the compilation of "ComeauTest.c".
    Is it necessary to have a user defined operator = and copy constructor for your classes? You didn't post the entire class interface, but a vector<vector<T> > is copyable without a user defined copy constructor / assignment operator.

    Regards,

    Paul McKenzie

  3. #3
    Join Date
    Apr 1999
    Location
    Altrincham, England
    Posts
    4,470
    Providing operator*() for your derived class does not "defeat the point of templates", it's correct programming. Your operator*() in the base class does not return an object of the derived class, so it would be dangerous to arbitrarily allow the assignment (due to potential slicing problems).

    The alternative - if you know that the derived class will never add new member variables is to overload operator= on the base class template: i.e.
    Code:
    WavesetTime& WaveSetTime::operator=(const WavesetTemplate<T>& rhs);
    However, if you ever add new data to WaveSetTime, then this code could introduce a bug that may be extremely difficult to fix.

    You could try adding a virtual "multiply" member and call that from the external operator*(), but you will still have the slicing problem during assignment.On second thoughts, though - that still won't work because you run in to the problem of multiplying different derived classes together because they both match the base class reference.

    I should also say that it's my experience that overloaded arithmetic operators and class hierarchies don't mix - the problems are many and tricky. IIRC Herb Sutter has quite a bit to say on this in his Exceptional C++ books
    Correct is better than fast. Simple is better than complex. Clear is better than cute. Safe is better than insecure.
    --
    Sutter and Alexandrescu, C++ Coding Standards

    Programs must be written for people to read, and only incidentally for machines to execute.

    --
    Harold Abelson and Gerald Jay Sussman

    The cheapest, fastest and most reliable components of a computer system are those that aren't there.
    -- Gordon Bell


  4. #4
    Join Date
    Jan 2004
    Posts
    29
    Thanks for your replies. However there are still some issues which aren't clear:

    1-Apart from operator=, 'the books' say operators declared in templates can be inherited. However, my implementation of the binary operator* is (I think) 'textbook' yet it does not compile... What do I have to do to get it to compile?

    I do see your point though about the incompatible return type from operator*, but again, how can inhertance work in this case?

    (Re why define operator= and copy ctor: there are other data members in the template which are defined on the heap).

    2-Good point about the data slicing. I cannot use the derived class specialisation in the base (template) class, because I actually have another derived class, namely WavesetFrequency : public WavesetTemplate<complex<double> >.

    As it happens I'm not adding any other data in the derived classes, and I will probably now ditch the derived classes in favour of just the template class and specialise functions for specific template parameters. ( but would still be interested to hear a reply to my fist point)

    Thanks for the book ref - I'll have a look.

    Thank you.

    Jon

  5. #5
    Join Date
    Apr 1999
    Location
    Altrincham, England
    Posts
    4,470
    As I said before, it's my experience that overloaded operators and inheritance hierarchies don't sit happily together. Scott Meyers puts it quite succinctly:
    Of course, just because you can overload these operators is no reason to run off and do it. The purpose of operator overloading is to make programs easier to read, write and understand, not to dazzle others with your knowledge that comma is an operator. If you don't have a good reason for overloading an operator, don't overload it.
    (More Effective C++, page 38). Elsewhere (I can't locate it at the moment) Meyers also gives the advice to only overload an operator if its meaning is similar to "accepted usage". He uses the phrase "do as the ints do". What he means is: if you do overload an operator, make sure that your meaning is analogous to its meaning if you replace your class with ints. (The only departure from this is the use of << and >> by the iostreams library.)

    What this boils down to is that, in the general case, if you have a class hierarchy, then usually you don't have arithmetic operations on classes in that hierarchy - they just don't go together. The reason for this is that class hierarchies tend to be used polymorphically, but overloaded operators are designed for "value" use. Therefore, if you do have a class hierarchy, you need to provide polymorphic operations for those classes, and overloaded operators don't really fit the bill.

    The alternative (if operators really do make sense) is to rethink whether or not you really need a (public) class hierarchy.

    --------

    I don't know whether this will help or not, but have you heard of the Curiously Recurring Template Pattern? It goes a bit like this:
    Code:
    template <class derived>
    class foo
    {
        // ...
    };
    
    class bar : public foo<bar>
    {
        // ...
    };
    It's an interesting pattern, which I'll leave you to ponder on if you've not come across it before (it's quite rewarding to come up with uses for it by yourself).

    I will suggest this though (and this is off the top of my head, with no testing, so no guarantees):
    Code:
    template<class derived>
    class foo
    {
    public:
        derived operator*(const derived& rhs)
        {
            return mult(rhs);
        }
    
    protected:
        virtual derived mult(const derived& rhs)
        {
            // ...
        }
    };
    
    class bar : public foo<bar>
    {
        //...
    protected:
        bar mult(const bar& rhs)
        {
            // ...
        }
    };
    As I say, I've not tried this out, so I'm not sure if it will even work (there may be issues with function matching), but it may be worth investigating.
    Correct is better than fast. Simple is better than complex. Clear is better than cute. Safe is better than insecure.
    --
    Sutter and Alexandrescu, C++ Coding Standards

    Programs must be written for people to read, and only incidentally for machines to execute.

    --
    Harold Abelson and Gerald Jay Sussman

    The cheapest, fastest and most reliable components of a computer system are those that aren't there.
    -- Gordon Bell


  6. #6
    Join Date
    Jan 2004
    Location
    Düsseldorf, Germany
    Posts
    2,401
    If your z=x*y example is something that you will do a lot, you might want to think about the following alternative design:

    Code:
    class WavesetTime : public WavesetTemplate<double>
    {
    public:
      WavesetTime(){} ;
      WavesetTime( const WavesetTime& x, const WavesetTime& y) // implements x*y
      [...]
    }
    
    WavesetTime x,y;
    x.SetWave(0.0,flat10,0.1); //set some data
    y.SetWave(0.0,ramp,0.1); //set some data
    WavesetTime z(x, y);
    That should definitely compile (although I didn't test) and is imho the better design, as you don't call the default
    constructor for z followed by operator=, but just call the constructor you need.

  7. #7
    Join Date
    Jan 2004
    Posts
    29
    Thanks for all your suggestions and comments. Much appreciated.

    Regarding the last post, I can implement operator* in the derived class (WavesetTime) and this will work, and will be more readable/useable as a class. In my case clarity is more important than efficiency.
    The main point of my original question was that I wanted to use the functionality of the binary operators, defined in the template, in the derived classes through inheritance, without redefining them all in the derived classes.

    I am convinced by the data slicing argument against inheriting operators (although it would not be an issue here since the derived classes do not have additional data), and will change the design of my class accordingly.

    Regarding my original question about the compile error:
    Code:
    WavesetTime& operator=(const WavesetTemplate<double>& rhs)
    in the derived (WavesetTime) class sorts out the compile error.

    Cheers

    Jon

  8. #8
    Join Date
    Apr 1999
    Location
    Altrincham, England
    Posts
    4,470
    It doesn't solve the problem for the general case, though.
    Code:
    class foo
    {
        // ...
    };
    
    class bar1 : public foo
    {
    public:
        bar1& operator=(const foo&);
    
    private:
        // additional data
    };
    
    class bar2 : public foo
    {
    public:
        // etc
    
    private:
        // additional data (different to bar1)
    };
    
    int main()
    {
        bar1 b1;
        bar2 b2;
    
        // ...
    
        b1 = b2; // Oops!
    }
    As you say, you can get away with it as long as your derived classes don't declare extra member data.

    Also, don't forget the rule of three - if you need one of copy ctor, copy assign or destructor, then you probably need all three.

    You might also have a look at your member data - if all the members are themselves properly copyable/assignable, then you don't need to write your own assignment operator - the compiler will provide a perfectly adequate one for you. This is often the simplest solution to copying/assignment - look after the members, and the class will take care of itself. (Of course, you apply this rule recursively.) Again. in general, you only really need a copy ctor/assignment/dtor if your class handles some sort of resource such as assigned memory. Even then, it's often possible to wrap the resource in a simple class (with copy ctor, etc) and lose the need for one in your "main" class.

    As an example, compare these two classes:
    Code:
    class foo1
    {
        // ...
    
    private:
        int* int_array;
    };
    
    class foo2
    {
        // ...
    
    private:
        std::vector<int> int_array;
    };
    foo1 needs copy ctor/copy assign/dtor, foo2 doesn't, because std::vector<> is properly copyable.
    Correct is better than fast. Simple is better than complex. Clear is better than cute. Safe is better than insecure.
    --
    Sutter and Alexandrescu, C++ Coding Standards

    Programs must be written for people to read, and only incidentally for machines to execute.

    --
    Harold Abelson and Gerald Jay Sussman

    The cheapest, fastest and most reliable components of a computer system are those that aren't there.
    -- Gordon Bell


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured