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:
- Templates are used to help keep the DRY principle.
- 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 double
s.
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 double
s, 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).