Home > Mobile >  Understanding template function declarations
Understanding template function declarations

Time:11-11

First place I saw syntax I didn't understand was here.

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

Had a couple other small questions as I read this.

template <class T>
class mypair {
    T a, b;
  public:
    mypair (T first, T second)
      {a=first; b=second;}
    T getmax ();
};

template <class T>
T mypair<T>::getmax ()
{
  T retval;
  retval = a>b? a : b;
  return retval;
}

What I (think) I understand:

  1. Templates are used to help keep the DRY principle.
  2. Templates have the common attributes across classes that you want to be able to use.

Let's start with the first code example.

To me, it looks like there are two return types but I think I'm supposed to read it as "this is a template function with <typename T> where T is the class. Therefore, only for this function, declare T as a class which will be determined at compile time and will by the class called T. This function will return an int.

Is that a correct understanding of the function declaration? It seems strange to have to declare template <class T> but it looks like when you have more than one template class you list them like so template <class T, class U>.

Where in the code do we actually declare what attributes T has and fit under that template? Is that what mypair is being declared as in the next block? If I wanted mypair to also be considered a U would I just say template <class T, class U> class mypair {...

Second code example.

The specificate part I'm trying to understand is the declaration for getmax template <class T> T mypair<T>::getmax ()

I think mypair<T>::getmax() is saying that we're declaring the getmax function for mypair. What does the T in mypair<T> refer to?

Final clarification question.

From what I read, it sounds like the main complaint about using templates is that they can increase compile time but it sounds like they have no impact on run time. Right?

CodePudding user response:

Let's start with:

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

Say you call sgn(42). Because 42 is an int, sgn<int> will be need to be instantiated so the signature becomes int sgn(int val) to match the call. Then the body becomes return (int(0) < val) - (val < T(0));, which makes two boolean tests:

  • is 0 less than val? (another way of saying, is val > 0?)
  • is val less than 0?

Each of these tests yields a true or false boolean result, and in an arithmetic context (which is created by using a - on the boolean values), true implicitly converts to 1, and false implicitly converts to 0.

So, the possible values are:

             (0 < val) - (val < 0)
val > 0      (true - false) == 1 - 0 == 1
val == 0     (false - false) == 0 - 0 == 0
val < 0      (false - true) == 0 - 1 == -1

So the sgn function returns 1 if val is positive, 0 if it's 0, and -1 if it's negative. Put another way, it returns an int indicative of the sign of val.

If you'd instantiated sgn for another type - say double - you'd get logically equivalent behaviour for doubles from a distinct instantiation of the template, with machine code instructions suitable for comparing doubles.

Where in the code do we actually declare what attributes T has and fit under that template?

We don't declare what attributes T has to have, but it is implicit in the use we make of T and the val variable of T type: firstly, there's the T(0) construction that must be supported, and secondly, we must be able to use < to compare T values and get a boolean result back (well, there's nothing checking it returns a bool, but that's normal for operator< and if it returned something else, the - logic would probably break).

Is that what mypair is being declared as in the next block?

No - the T used for sgn is distinct from any other use of T in the program.

Templates have the common attributes across classes that you want to be able to use.

It's hard to even know what you're thinking about there. A template like sgn doesn't even involve a class.


Discussing the other code snippet:

template <class T>
class mypair {
    T a, b;
  public:
    mypair (T first, T second)
      {a=first; b=second;}
    T getmax ();
};

template <class T>
T mypair<T>::getmax ()
{
  T retval;
  retval = a>b? a : b;
  return retval;
}

Above, getmax is defined out-of-line, but all that is equivalent to:

template <class T>
class mypair {
    T a, b;
  public:
    mypair (T first, T second)
    {a=first; b=second;}

    T getmax() {
      T retval;
      retval = a>b? a : b;
      return retval;
    }
}

When you call the constructor, it will work out a unifying type based on the first and second arguments. For example, if you called with mypair{12, 45.2} it will decide that T = double is suitable, and instantiate the class template for double. You can then simply think of the behaviour the code would have by mentally replacing T everywhere with double. You can also explicitly instantiate a template: mypair<double>{4, 8} would have T, and therefore a and b, be doubles, so the return type of getmax() would also be a double. (The getmax() function should be made const, as it doesn't need to mutate any data members).

  • Related