Topical Information

The purpose of this quiz is to give you a chance to focus your knowledge of passing functions as arguments in C/C++.

Quiz Information

Questions

  1. Indicate the (pointer) type of each of the following functions:

    1. double pow(double base, double exponent);
      
          double (*)(double,double)
      
      
    2. int tolower(int ch);
      
          int (*)(int)
      
      
    3. time_t time(time_t * tim);
      
          time_t (*)(time_t*)
      
      
    4. long get_line(char s[], long len, istream & in = cin);
      
          long (*)(char*,long,istream&)
      
      
    5. string make_box(const string & s, char border, char filler);
      
          string (*)(const string&, char, char)
      
      
    6. bool already_past(const Date & d);
      
          bool (*)(const Date&)
      
      
    7. long labs(long x);
      
          long (*)(long)
      
      
  2. 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.
    
    
  3.  
    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...
  4. 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]))
    
    
  5. 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:

    1. would sort instantiate with this argument (and an appropriate array)?
    2. does the caller have to take any special care to pass this function?
    3. would sort work — actually sort the elements?
        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)
    
    
  6. 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!
    
    
  7. 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.
    
    
  8. 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.