Search

14.2 — Overloading operators to make function templates work with class types

In lesson %Failed lesson reference, id 11255%, we discussed how the compiler will use function templates to instantiate functions, which are then compiled. We also noted that these functions may not compile, if the code in the function template tries to perform some operation that the actual type doesn’t support (such as adding integer value 1 to a std::string).

In this lesson, we’ll take a look at a few examples where our instantiated functions won’t compile because our actual class types don’t support those operators, and show how we can define those operators so that the instantiated functions will then compile.

Operators, function calls, and function templates

First, let’s create a simple class:

and define a max function template:

Now, let’s see what happens when we try to call max() with object of type Cents:

C++ will create a template instance for max() that looks like this:

And then it will try to compile this function. See the problem here? C++ has no idea how to evaluate x > y when x and y are of type Cents! Consequently, this will produce a compile error.

To get around this problem, simply overload operator> for any class we wish to use max with:

This works as expected.

Another example

Let’s do one more example of a function template not working because of missing overloaded operators.

The following function template will calculate the average of a number of objects in an array:

This produces the values:

3
5.535

As you can see, it works great for built-in types!

Now let’s see what happens when we call this function on our Cents class:

The compiler goes berserk and produces a ton of error messages! The first error message will be something like this:

c:test.cpp(45) : error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'Cents' (or there is no acceptable conversion)

Remember that average() returns a Cents object, and we are trying to stream that object to std::cout using operator<<. However, we haven’t defined the operator<< for our Cents class yet. Let’s do that:

If we compile again, we will get another error:

c:test.cpp(14) : error C2676: binary '+=' : 'Cents' does not define this operator or a conversion to a type acceptable to the predefined operator

This error is actually being caused by the function template instance created when we call average(Cents*, int). Remember that when we call a templated function, the compiler “stencils” out a copy of the function where the template type parameters (the placeholder types) have been replaced with the actual types in the function call. Here is the function template instance for average() when T is a Cents object:

The reason we are getting an error message is because of the following line:

In this case, sum is a Cents object, but we have not defined operator+= for Cents objects! We will need to define this function in order for average() to be able to work with Cents. Looking forward, we can see that average() also uses the operator/=, so we will go ahead and define that as well:

Finally, our code will compile and run! Here is the result:

11 cents

Note that we didn’t have to modify average() at all to make it work with objects of type Cents. We simply had to define the operators used to implement average() for the Cents class, and the compiler took care of the rest!

%Missing lookup for lesson id 201%

14 comments to 14.2 — Overloading operators to make function templates work with class types

  • DOBRESCU Mihai

    Hi,

    For the first part, maybe it would be clearer for the reader if a sample program were provided. Here is the code:


    #include

    template // this is the template parameter declaration
    Type max(Type tX, Type tY)
    {
    return (tX > tY) ? tX : tY;
    }

    using namespace std;

    class Cents
    {
    private:
    int m_nCents;
    string m_sName;
    public:
    Cents(int nCents, string sName)
    : m_nCents(nCents), m_sName(sName)
    {
    }

    int GetCents()
    {
    return m_nCents;
    }

    string GetName()
    {
    return m_sName;
    }

    friend bool operator>(Cents &c1, Cents&c2)
    {
    return (c1.m_nCents > c2.m_nCents) ? true: false;
    }
    };

    int main()
    {
    Cents cNickle(5, "A nickle");
    Cents cDime(10, "A dime");

    Cents cBigger = ::max(cNickle, cDime);

    cout << cBigger.GetName() << " is bigger." << endl;

    return 0;
    }

    And here is the output:


    A dime is bigger.

    Also, apparently the easiest way to get rid of the namespace collision is to prefix the function name with the scope resolution operator ("::"), so that the std namespace is avoided and the global namespace is used in order to search for the function:


    Cents cBigger = ::max(cNickle, cDime);

    Again, it would be easier to understand for the reader if the second example also had listed a full sample program, like in the following example:


    #include

    using namespace std;

    class Cents
    {
    private:
    int m_nCents;
    public:
    Cents(int nCents)
    : m_nCents(nCents)
    {
    }

    int GetCents()
    {
    return m_nCents;
    }

    friend ostream& operator<< (ostream &out, const Cents &cCents)
    {
    out << cCents.m_nCents << " cents ";
    return out;
    }

    void operator+=(Cents cCents)
    {
    m_nCents += cCents.m_nCents;
    }

    void operator/=(int nValue)
    {
    m_nCents /= nValue;
    }
    };

    template
    T Average(T *atArray, int nNumValues)
    {
    T tSum = 0;
    for (int nCount=0; nCount < nNumValues; nCount++)
    tSum += atArray[nCount];

    tSum /= nNumValues;
    return tSum;
    }

    int main()
    {
    int anArray[] = { 5, 3, 2, 1, 4 };
    cout << Average(anArray, 5) << endl;

    double adArray[] = { 3.12, 3.45, 9.23, 6.34 };
    cout << Average(adArray, 4) << endl;

    Cents cArray[] = { Cents(5), Cents(10), Cents(15), Cents(14) };
    cout << Average(cArray, 4) << endl;

    return 0;
    }

    And its output:


    3
    5.535
    11 cents

    The question that still remains is the following one: why did not the compiler complain when it encountered the following line:


    T tSum = 0;

    That is, what happens when the following assignment takes place?


    Cents tSum = 0;

    Also, templating resembles a little bit preprocessing. Does it happen like in preprocessing? Is the text replaced somewhere with other excerpts of text, that are subsequently compiled? Or, how otherwise do the function template instances get compiled into binary code? I assume that there must be some temporary text files somewhere that contain the function template instances.

  • Tom

    I think on line 5 of 'Another example' , in the section: 'Now let’s see it in action:',

    'cout << Average(dnArray, 4) << endl;'
    should actually be

    'cout << Average(adArray, 4) << endl;'.
    imho of course.

    Since it's a double now as opposed to an int, a la
    'cout << Average(anArray, 4) << endl;'
    like in line 2. I could be mistaken, I think my logic is good.

    T

  • Prashant Patel
    what is difference between template <typename T>  and template  <class T>.
    Form me both are genrating same result.
    
  • Hi Alex,

    could you tell me why there is a friend for the << operator?

        
    friend ostream& operator<< (ostream &out, const Cents &cCents) 
        
    { 
            
    out << cCents.m_nCents << " cents "; 
            
    return out; 
        
    } 

    Thanks

    • Conrado

      Read http://www.learncpp.com/cpp-tutorial/92-overloading-the-arithmetic-operators/ to understand the use of "friend function" to overload an operator.

  • AndiW

    Hi Alex,

    excellent tutorial! I found a little typo though. The
    Last return statement in the last block of code in the
    "Operators, function calls, and function templates" is

    return (c1.m_nCents > c2.m_nCents) ? true : false;

    but it should be

    return (c1.m_nCents > c2.m_nCents) ? c1 : c2;

    Also, the return type shouldn't be bool, but Cents.
    Otherwise it does not fit the max-template. Right?

    Thanks!

    Andreas

    • Andreas, I believe the code is correct as written. We're not trying to fit the max-template here, but rather we're overloading the > operator so that the compiler knows how to do c1 > c2 when it encounters that inside the max() template for Cents.

      Consider the logical meaning of the following statment:

      if (c1 > c2)
      

      if the > operator returns a bool here, then c1 > c2 returns a bool here and this makes sense. But if it returns a Cents instead, what does that mean logically?

  • MarkG

    Hi Alex,

    If I use

    friend std::ostream& operator<< (std::ostream &out, Cents &cCents);
    {
    out << cCents.m_nCents << " cents ";
    return out;
    }
    

    as my << overide definition within the Cents class, then the line

    std::cout << Average(cArray,4) << std::endl;
    

    in the main function produces an error in my compiler (see below), which I
    think is related to the chaining of the << operator.

    error: no match for ‘operator<<’ in ‘std::cout << Average [with T = Cents](((Cents)(& cArray)), 4)’

    If the Average function returned a *this pointer I think this would solve
    the problem (alternatively, I can introduce a dummy Cents variable to
    store the result of Average, and std::cout that). However, another way to
    resolve this problems is to rewrite the << override function with a const
    input for Cents, like so:

    friend std::ostream& operator<< (std::ostream &out, const Cents &cCents);
    

    Now the line

    std::cout << Average(cArray,4) << std::endl;
    

    compiles without a problem! Can you help me understand why the addtion of
    the const keyword to the input resolves the problem of chaining the output
    of the Average function for Cents? Also, would it be possible to modify the
    template Average function so that for classes it returned a *this pointer
    rather than a value? I suspect not as the function is outside of the Cents
    class, but would be interested to know. This appears to be the same problem
    as Ben (post #1) had.

    Thanks,

    Mark

    P.S. Fantastic tutorial by the way!

    • First off, I changed the example to use a const Cents &cents, which it should have been all along, because operator<< is not modifying the Cents it's using.

      As to why your example didn't work, let's start with a simpler one. Consider the following statement:

      std::cout << Cents(5) << std::endl;

      Would you expect this to work if operator<< didn't take a const Cents reference?

      The answer should be no. Cents(5) is an rvalue, and does not have it's own address. Thus, you can't set a reference to it.

      Because Average() is returning a Cents by value, it's falling into the same trap. It's the exact same reason the following doesn't work:

      void foo(int &x)
      {
      }
      
      foo(5); // doesn't work
      

      However, if you make the reference const, then you CAN pass in literals and temporaries.

      void foo(const int &x)
      {
      }
      
      foo(5); // works
      

      and similarly, the return-by-value Cents from Average works as well.

      As to your second question, you can't return *this from Average because it's not a member function.

  • Hi Alex,

    Another small typo, just after the introduction of the Cents class the text mentions the "height" class.

  • Ben

    A little Annotation to your final output:
    5+10+15+14 = 44
    And 44/4 = ?
    Well at least not 14 ;)
    (assuming you calculate in base 10)

    [ Fixed! -Alex ]

  • Ben

    Still, after adding all necessary operators, i got problems compiling. The solution was to add a const in the definition of operator<< i.e. changing the following code

    friend ostream& operator<< (ostream &out, Cents &cCents)
    {
    out << cCents.m_nCents << " cents ";
    return out;
    }

    to

    friend ostream& operator<< (ostream &out, const Cents &cCents)
    {
    out << cCents.m_nCents << " cents ";
    return out;
    }

    .

    Unfortunatly, i don't know, why this change has to be made, maybe someone can explain that.
    Annotation:
    Another way to change the code that way, that it works is to explicitly cast the output of the Average() funtion to Cent, i.e. to write the following code:

    cout << Cent(Average(cArray, 4)) << endl;

    Edit: pre tags still do not work

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">