Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Function not overridden when using templates

I'm in the final stages of writing a small Calendar library for a school assignment and I have run into an unexpected and very confusing problem; my assignment operator is not overridden when I introduce templates!

So the structure is the following: I have an abstract class Date with mostly pure virtual functions (including the assignment operator), and then I have two subclasses Gregorian and Julian that implement all its functions. Finally, I have written a template for a class Calendar, which contains today's date (in the form of a Gregorian or Julian object) and some other stuff that aren't really relevant for this particular problem.

The problem is that when trying to set this member today I get a long linker error:

Error 4 error LNK2019: unresolved external symbol "public: virtual class lab2::Date & __thiscall lab2::Date::operator=(class lab2::Date const &)" (??4Date@lab2@@UAEAAV01@ABV01@@Z) referenced in function "public: class lab2::Gregorian & __thiscall lab2::Gregorian::operator=(class lab2::Gregorian const &)" (??4Gregorian@lab2@@QAEAAV01@ABV01@@Z) C:\Users...\test.obj Calendar

telling me that it can't find the function operator= in the Date class (obviously because it's purely virtual). Why isn't it using any of the overridden ones? It's telling me that Gregorian::operator= is trying to call Date::operator=?

Here's the simplified code where things go wrong:

namespace cal_lib {
    template <typename T>
    class Calendar {
    public:
        Calendar() {
            today = T(); // this yields the error
        }
    private:
        T today;
    };
 }

And here's a snippet from Gregorian.cpp:

namespace cal_lib {
    class Gregorian : public Date {
    public:
        Gregorian();
        virtual Gregorian& operator=(const Date& date);
        virtual Date& add_year(int n = 1);
        virtual Date& add_month(int n = 1);
    };

    // here I'm using Date's constructor to get the current date
    Gregorian::Gregorian() {}

    Gregorian& Gregorian::operator=(const Date& date) {
        if (this != &date) {
            // these member variables are specified as
            // protected in Date
            m_year = 1858;
            m_month = 11;
            m_day = 17;

            add_year(date.mod_julian_day()/365);
            add_month((date.mod_julian_day() - mod_julian_day())/31);
            operator+=(date.mod_julian_day() - mod_julian_day());
        }
    }
}

The (default) constructor of Date simply sets the values of m_year, m_month and m_day to today's date:

Date::Date() {
    time_t t;
    time(&t);
    struct tm* now = gmtime(&t);
    m_year = now->tm_year + 1900;
    m_month = now->tm_mon + 1;
    m_day = now->tm_mday;
}

It's worth noting though that this works perfectly fine:

Gregorian g;
Julian j;
g = j; // no problems here

Date* gp = new Gregorian();
Date* jp = new Julian();
*gp = *jp; // no problems here either

This is how the Calendar class is instantiated:

using namespace cal_lib;

Calendar<Gregorian> cal;

Is there some very obvious mistake here that I'm doing?

like image 432
hagward Avatar asked Nov 01 '12 19:11

hagward


People also ask

What happens when a template is overloaded with a non-template function?

When a function template is overloaded with a non-template function, the function name remains the same but the function’s arguments are unlike. Below is the program to illustrate overloading of template function using an explicit function: Explicitly display: 200 Displaying Template: 12.4 Displaying Template: G

What are templates in C++?

Templates are powerful features of C++ which allows us to write generic programs. We can create a single function to work with different data types by using a template. A function template starts with the keyword template followed by template parameter (s) inside <> which is followed by the function definition.

How do you call an overridden function from a derived class?

We can also access the overridden function by using a pointer of the base class to point to an object of the derived class and then calling the function from that pointer. accesses the print () function of the Base class. In this program, we have called the overridden function inside the Derived class itself.

What is function overriding in C++?

The derived classes inherit features of the base class. Suppose, the same function is defined in both the derived class and the based class. Now if we call this function using the object of the derived class, the function of the derived class is executed. This is known as function overriding in C++.


3 Answers

If you carefully read the error message you will notice two things, the compiler is trying to find a definition for:

Date& operator=(const Date&)

And the symbol is needed from the definition of:

Gregorian& operator=(const Gregorian&)

*So how did that operator even appear in your program? *

Copy constructors and assignment operators are special, and they will always be declared in the program. Either you provide a declaration or the compiler will do it for you. You have provided Gregorian& Gregorian::operator=(const Date&) but that does not remove Gregorian& Gregorian::operator=(const Gregorian&) from the program.

When you try to assign one Gregorian object to another, the compiler will find the two overloads yours and the implicitly defined, and overload resolution will find that the implicitly declared assignment is a better match. That will trigger the definition of the assignment operator in a manner similar to:

T& operator=( T const & o ) {
   Base::operator=(o);           // for all bases
   member=o.member;              // for all members
}

There are different things that you can do to solve this issue. The simplest is probably defining Date Date::operator=(const Date&) in your program (leave it as a pure virtual function, but also provide a definition). This way when the compiler encounters two objects of the same derived type it can do its magic and everything will work. You can also use this as means of factoring away the implementation of the copy of the base's members, by forcing dispatch in the derived type.

Another option that takes more effort is to actually declare and define the assignment operator for all of the derived types. There is more code to be written here, and the code that handles the members of Date will need to be duplicated, if you modify the base class you will need to update all of the derived assignment operators... I would not go through that path.

Finally, and once the compiler error is fixed, consider whether your implementation of the assignment operator makes sense or not for a general Date (which might actually be a Gregorian date). Consider what happens if you do:

Gregorian d1 = ...; // some date
Gregorian d2 = d1;  // same date
Date &d = d2;
d1 = d2;            // What date does 'd1' represent??

Note that the issue in this example goes away if your provide the definition of Date::operator= and let the compiler generate Gregorian::operator=(Gregorian const&) for you.

like image 106
David Rodríguez - dribeas Avatar answered Oct 11 '22 08:10

David Rodríguez - dribeas


First of all: While you have public Date& Date::operator=(const Date&) in your Date class, compiler generates implicit function Gregorian& Gregorian::operator=(const Gregorian&) for your Gregorian class.

The compiler-generated function behaves like:

Gregorian& Gregorian::operator=(const Gregorian& g)
{
    Date::operator=(g);
    return *this;
}

According to overload resolution rules, this function is better macth in case

Gregorian g;
Gregorian u;
g = u;

then Date& Date::operator=(const Date&)

If you delete this function or make private, it still will be declared and would be better match for compiler, because compiler ignores accessibility issues when choosing overload:

[Note: The function selected by overload resolution is not guaranteed to be appropriate for the context. Other restrictions, such as the accessibility of the function, can make its use in the calling context ill-formed. —end note ]

(13.3 Overload resolution, C++11 standard draft)

You probably should write realization for Date operator= function. You probably should have same underlining data format for all calendar realization, then you wouldn't need virtual operator=. Now you are trying to perform some conversion, but to do it correctly, you have to know not only type of left operator= operand, but type of right operand also.

like image 30
Lol4t0 Avatar answered Oct 11 '22 09:10

Lol4t0


The simplified code illustrating your problem:

class A {
public:
  virtual A& operator = (const A& a) = 0;
};

class B : public A {
public:
  virtual B& operator = (const A&)
  {
      return *this;
  }
};

int main() 
{
  B b;
  b = B();
}

http://liveworkspace.org/code/add55d1a690d34b9b7f70d196d17657f

Compilation finished with errors:
/tmp/ccdbuBWe.o: In function `B::operator=(B const&)':
source.cpp.cpp:(.text._ZN1BaSERKS_[_ZN1BaSERKS_]+0x14): undefined reference to `A::operator=(A const&)'
collect2: error: ld returned 1 exit status

You think you call this

virtual B& B::operator = (const A&)
//                              ^

but actually what is called is this, auto generated default operator:

  virtual B& B::operator = (const B&)
   //                             ^

Its default implementation calls this operator:

  virtual A& operator = (const A& a) = 0;

which is not implemented - so the error.

The simplest solution is to make explicit cast in assignment:

int main() 
{
  B b;
  b = static_cast<const A&>(B());
}

I would suggest - not to define A operator as virtual - it makes no sense here.

If your really want it virtual, then either implement also its base pure virtual version:

class A {
public:
  virtual A& operator = (const A& a) = 0;
};
inline A& operator = (const A& a) { return *this; }

Or - delete default operator = from all derived from class A - which is very inconvenient to do...

like image 1
PiotrNycz Avatar answered Oct 11 '22 09:10

PiotrNycz