The purpose of this quiz is to give you a chance to focus your knowledge of passing functions as arguments in C/C++.
Indicate the (pointer) type of each of the following functions:
double (*)(double,double)
int (*)(int)
time_t (*)(time_t*)
long (*)(char*,long,istream&)
string (*)(const string&, char, char)
bool (*)(const Date&)
long (*)(long)
In C, passing functions as arguments had to be done via such function pointers as above. This was tricky and nasty. How do we avoid this business in C++? Are the function pointers really gone? Explain.
In C++ we use the template mechanism to avoid having to use function pointers raw like that. The compiler deduces the function pointer type for us and checks it against the operations necessary for our function parameter typename. The function pointers are not truly gone — just embedded by the compiler inside a typename and handled in a more automated fashion.
TRUE ✓ | FALSE ✗ | It is terribly easy to point to a normal, globally accessible function. | ||
---|---|---|---|---|
TRUE ✓ | FALSE ✗ | However, class methods are a bit trickier. | ||
TRUE ✓ | FALSE ✗ | It is so difficult, Jason wouldn't even show us how it was done! | ||
TRUE ✓ | FALSE ✗ | That Jason, what a nice guy... |
Given the following sort function, add a template argument to replace the comparison of data. It should tell whether or not the elements are out of order. (You need only show the parts of the function that need to change — to save time and space.)
template <typename ArrType, typename PosType> void sort(ArrType & array, PosType start, PosType end) { PosType pass, cur; bool did_a_swap = true; pass = 0; while ((pass < end-start+1) && did_a_swap) { did_a_swap = false; for (cur = 0; cur < end-1; cur++) { if (array[cur] < array[cur+1]) { did_a_swap = true; swap(array[cur], array[cur+1]); } } pass++; } return; }
template <typename ArrType, typename PosType, typename OrderFunc> void sort(ArrType & array, PosType start, PosType end, const OrderFunc & out_of_order) if (out_of_order(array[cur], array[cur+1]))
What order did the original sort (above) sort into (ascending or descending)?
If the data in the given array were unique, descending. But since the odds of that are slim, the original sort order was non-increasing.
How many lines did you have to change to add your function argument?
Just three. The template head, the argument list, and the if test inside the inner loop.
What operations are expected of your new template argument?
operator() taking two arguments of the result type from the operator[] of the ArrType.
Look over the following functions and answer the following questions about each:
inline long add_n(long x, long n) { return x+n; } bool greater(long x, long y) { return x > y; } template <typename DataType> bool greater(const DataType & x, const DataType & y) { return x > y; }
a) add_n: i) yes
ii) no
iii) no; all element pairs would swap regardless of
value unless both were 0s
b) greater plain: i) yes
ii) no
iii) yes; changes the order from the original
to non-decreasing (ascending if unique)
c) greater template: i) yes
ii) yes; the argument must be explicitly
instantiated to match the array base
type
iii) yes; same as b)
Recall that a function object is an object whose class overloads the function call operator (operator()) at least once.
Show a function object version of the greater function above (the one that isn't a template). (Hints: How many data members will it have? Will it need constructors? How many operator()'s will it have? What advantages does/can this function object have over the original function?
class Greater { public: bool operator()(long a, long b) const { return a > b; } }; Advantages: none at this time; it is actually more syntax/typing!
Explain the use of the following function object class. What is its purpose? How might it be used in a program? Etc.
class Sum { double sum; public: Sum(double s = 0.0) { reset(s); } Sum(const Sum & s) { reset(s.sum); } double operator() (double num) { return sum += num; } double operator() (void) const { return sum; } void reset(double s = 0.0) { sum = s; return; } };
This function object class adds up a sequence of values passed to it through its first operator(). The sum is stored in a member variable and returned from the second overload of operator(). The current sum can be set via a constructor parameter or by calling the reset function later in the program.
Sometimes we want to pass a function object by reference (to be able to change class member variables, for instance).
However, many compilers won't allow regular function (pointer)s to pass through this reference argument because they consider them to be inviolable and won't allow a reference to be made to them (because they could be redirected to point to a new memory location). To fix this, we can make the function argument a constant reference. But then the function object still wouldn't work because it would be a constant and couldn't change its member variables! For instance, the class Sum above would not work — since only its reporting method is const and we could not change its class data member.
So we'd change this function:
template <typename Data, typename Func> void foreach(Data & array, size_t count, Func & f) { for (size_t i = 0; i < count; ++i) { f(array[i]); } return; }
into this function:
template <typename Data, typename Func>
void foreach(Data & array, size_t count, const Func & f)
{
for (size_t i = 0; i < count; ++i)
{
f(array[i]);
}
return;
}
See how subtle the fix is for the function accepting the function argument?
How can we fix this situation — so that the function object can update its member variables and yet the function argument could still be a regular function (pointer)? Show what would be done to the Sum class above to adjust for this situation.
We can make the argument a constant reference instead of a pure reference. This disallows changing the argument under normal conditions. But then we change the necessary member variables of the function object class to mutable which allows them to change even inside a const object. Finally, mark all necessary operator() as const as well. For the Sum class, we would do this: class Sum { mutable double sum; public: double operator() (double num) const { return sum += num; } }; Note that only two lines needed to change for this class.
Does anything have to be done to the call where we pass the function object?
No. The caller doesn't have to do anything differently and all should be well.