Navigation



14.2 — Function template instances

Function template instances

It’s worth taking a brief look at how template functions are implemented in C++, because future lessons will build off of some of these concepts. It turns out that C++ does not compile the template function directly. Instead, at compile time, when the compiler encounters a call to a template function, it replicates the template function and replaces the template type parameters with actual types! The function with actual types is called a function template instance.

Let’s take a look at an example of this process. First, we have a templated function:

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

When compiling your program, the compiler encounters a call to the templated function:

int nValue = max(3, 7); // calls max(int, int)

The compiler says, “oh, we want to call max(int, int)”. The compiler replicates the function template and creates the template instance max(int, int):

int max(int tX, int tY)
{
    return (tX > tY) ? tX : tY;
}

This is now a “normal function” that can be compiled into machine language.

Now, let’s say later in your code, you called max() again using a different type:

double dValue = max(6.34, 18.523); // calls max(double, double)

C++ automatically creates a template instance for max(double, double):

double max(double tX, double tY)
{
    return (tX > tY) ? tX : tY;
}

and then compiles it into machine language.

It’s worth noting that the compiler is smart enough to know it only needs to create one template instance per set of unique type parameters. It’s also worth noting that if you create a template function but do not call it, no template instances will be created.

Operators, function calls, and function templates

Template functions will work with both built-in types (eg. char, int, double, etc…) and classes, with one caveat. When the compiler compiles the template instance, it compiles it just like a normal function. In a normal function, any operators or function calls that you use with your types must be defined, or you will get a compiler error. Similarly, any operators or function calls in your template function must be defined for any types the function template is instantiated for. Let’s take a look at this in more detail.

First, we’ll create a simple class:

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

Now, let’s see what happens when we try to call our templated max() function with the Cents class:

Cents cNickle(5);
Cents cDime(10);

Cents cBigger = max(cNickle, cDime);

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

Cents max(Cents tX, Cents tY)
{
    return (tX > tY) ? tX : tY;
}

And then it will try to compile this function. See the problem here? C++ has no idea how to evaluate tX > tY! Consequently, this will produce a compile error.

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

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

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

Now C++ will know how to compare tX > tY when tX and tY are objects of the Cents class! As a result, our max() function will now work with two objects of type Cents.

Another example

Let’s do one more example of a function template. The following function template will calculate the average of a number of objects in an array:

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

    tSum /= nNumValues;
    return tSum;
}

Now let’s see it in action:

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

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

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:

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

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 cout using the << operator. However, we haven't defined the << operator for our Cents class yet. Let's do that:

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

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

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:

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

    tSum /= nNumValues;
    return tSum;
}

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

        tSum += atArray[nCount];

In this case, tSum is a Cents object, but we have not defined the += 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:

class Cents
{
private:
    int m_nCents;
public:
    Cents(int nCents)
        : m_nCents(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;
    }
};

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!

14.3 -- Template classes
Index
14.1 -- Function templates

17 comments to 14.2 — Function template instances

  • [...] Site news: Comment editing enabled! Recent Tutorials14.2 — Function template instances [...]

  • 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

  • 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 ]

  • Hi Alex,

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

  • 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.

  • 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?

  • 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

  • Prashant Patel
    what is difference between template <typename T>  and template  <class T>.
    Form me both are genrating same result.
    
  • 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

  • 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.

You must be logged in to post a comment.