CS 509, Advanced Programming II

Spring 2004 - Test 5


  1. According to Koenig and Moo, it's a good idea to define binary operators for a class outside the class if the class also defines conversions. Explain why.


    There's an asymmetry between the left and right operands of a binary operator defined within a class that doesn't exist when the binary operator is defined outside the class. Because the type of the left operand is used by the compiler to determine the class in which to find the operator's definition, the compiler won't apply any conversions to the operand. The right operand, however, may be freely converted as needed.

    $ cat t.cc
    struct bn {
      bn() { }
      bn operator + (const char *) const { return *this; }
      bn(const char *) { }
      };
    
    int main() {
      bn b;
      b = b + "1000000000000";
      b = "1000000000000" + b;
      }
    
    $ g++ -c -ansi -pedantic -Wall t.cc
    t.cc: In function int main():
    t.cc:12: no match for const char[14] + bn& operator
    
    $ CC -c t.cc
    "t.cc", line 12: Error: The operation "const char* + bn" is illegal.
    1 Error(s) detected.
    
    $
    

    Even though there is a conversion from character-array literals to big numbers, the compiler won't apply it in the second case when looking for the definition of + within any defined classes.

    When the operator is define outside the class, the compiler uses overload resolution to determine the proper operator implementation, and both operands may be freely converted as needed.

    $ cat t.cc
    struct bn {
      bn() { }
      bn(const char *) { }
      };
    
    bn operator + (const bn & a, const bn &) { return a; }
    
    int main() {
      bn b;
      b = b + "1000000000000";
      b = "1000000000000" + b;
      }
    
    $ g++ -c -ansi -pedantic -Wall t.cc
    
    $ CC -c t.cc
    
    $  
    

    Most of the answers to this question were wrong. Many of them mentioned something about temporaries, which is a good reason to avoid conversions all together, but doesn't really have anything to do with defining operators within or externally to a class.


  2. Given the following

    struct Device {
      bool init(int maj, int min);
      };
    
    struct FileSystem : Device {
      virtual bool init(int maj, int min);
      };
    
    struct Ext3 : FileSystem {
      bool init(int maj, int min);
      };
    

    indicate which version of init() gets called in each of the following:

    a) Device * dp = new Ext3();
       dp->init(3, 5);
    
    b) Ext3 fs3;
       FileSystem & fs = fs3;
       fs.init(3, 6);
    

    Be sure to explain your answers.


    This is straight off the lecture notes. For a, the first question is "is init() virtual?" dp's static type is Device; because Device is the top of the inheritance hierarchy and init() is non-virtual in Device, the answer is "No."

    The second question is "Where is init()?". Because Device::init() is non-virtual, we use static lookup to answer the question. Again dp's static type is Device and the search for init() starts and ends there.

    For b, fs's static type is FileSystem, and FileSystem::init() is virtual. fs's dynamic type is Ext3, and dynamic lookup finds Ext3::init().

    The correct answers, of which where were two, were exactly correct, while the wrong answers, of which there were two, were exactly backwards.


  3. The last test asked you to implement a template function that provided a function similar to a debugging-print macro

    #define debugp(msg) 
      if (debug_print) std::cerr << msg << ".\n"
    

    The answer to that question involved using a template value parameter to represent the boolean controlling the print statement.

    This test asks you to solve the same problem - implement a conditional debugging print statement using template functions - except this time your solution should use template specialization.


    By default, we want no debugging print statements, so the default debugging behavior should be to do nothing:

    template < bool print >
    inline void
    debugp(const std::string & msg) {
      }
    

    When we want debugging print statements, print will be true, and that's what we specialize on:

    template < >
    inline void
    debugp<true>(const std::string & msg) {
      std::cerr << msg << "\n";
      }
    

    Just to make sure:

    $ cat t.cc
    #include <iostream>
    #include <string>
    
    template < bool print >
    inline void
    debugp(const std::string & msg) {
      }
    
    template < >
    inline void
    debugp<true>(const std::string & msg) {
      std::cerr << msg << "\n";
      }
    
    int main() {
      debugp<DEBUGP>("Hey, it works!");
      }
    
    $ g++ -DDEBUGP=false -o t t.cc  
    
    $ ./t
    
    $ CC -DDEBUGP=false -o t t.cc
    
    $ ./t
    
    $ g++ -DDEBUGP=true -o t t.cc  
    
    $ ./t
    Hey, it works!
    
    $ CC -DDEBUGP=true -o t t.cc
    
    $ ./t
    Hey, it works!
    
    $ 
    

    No answers to this question were right, or even close to being right.


  4. A colleague of yours believes the following complete schedule

    move r0 a
    

    is not minimal and can be optimized into an empty schedule. Do you think your colleague is correct or not? Explain.


    Oh lucky students. It turns out your colleague was right because I messed up the question. The complete schedule should have been

    move a r0
    

    which is a bit harder to answer.

    All answers to this question were correct (possibly because I answered this question in an e-mail message), but some answers were more correct than others due to the quality of their explanation, which should have mentioned the effects of each schedule on storage.

    Before we leave this question, I want to share with you an answer I consider absolute gem of an example of the type of answers I occasionally get:

    I don't think this is not minimal.

    You might want to try your hand at figuring out if this is a correct or incorrect answer.



This page last modified on 27 April 2004.