CMSI 386
Homework #4
Partial Answers
  1. I wrote a little program to investigate:
    #include <iostream>
    using namespace std;
    
    struct {
      int n;
      char c;
    } A[10][10];
    
    int main() {
      cout << long(&(A[0][0])) << '\n';
      cout << long(&(A[3][7])) << '\n';
      cout << long(&(A[0][0].n)) << '\n';
      cout << long(&(A[0][0].c)) << '\n';
      cout << long(&(A[0][1])) << '\n';
    }
    

    This program displayed:

    4360810688
    4360810984
    4360810688
    4360810692
    4360810696
    

    The addresses we were asked to find are the first two. They are 296 bytes apart. This makes sense because on this machine, as we can see from the remaining outputs, that each element of A takes up 8 bytes (apparently 4 for the int and 4 for the char, which indicates padding for alignment). A[3][7] is the 37th element in the matrix, and 37 × 8 is exactly 296.

  2. Here are the answers, given as comments:
    double *a[n];     // array of n pointers to doubles
    double (*b)[n];   // pointer to array of n doubles
    double (*c[n])(); // array of n pointers to functions-returning-doubles
    double (*d())[n]; // function returning pointer to an array of n doubles
    
    Maybe you found David Anderson's Clockwise Spiral Rule page while you were trying to figure these out.

    In case you are interested, the much more readable Go equivalents are:

    var a [n]*float64;       // array of n pointers to doubles
    var b *[n]float64;       // pointer to array of n doubles
    var c [n]*func()float64; // array of n pointers to functions-returning-doubles
    var d func()*[n]float64; // function returning pointer to array of n doubles
    

    See the playground page I made for this. By the way, if you thought this problem was hard, you can make yourself feel better by reading this page from the Go Blog.

  3. You gotta use the clockwise spiral rule to decipher this. Also, make a note to yourself to not write code like this. Let’s break it down. We were given:
    double (*f(double (*)(double, double[]), double)) (double, ...);
    

    First, note that C++ often makes no real distinction between pointers and pointers to functions. So we can just say this:

    • The function f (1) takes in a function g and a double x, and (2) returns a function h.
    • The function g (1) takes in a double and a double array, and (2) returns a double.
    • The function h (1) takes in at least one double, and (2) returns a double.

    Super bouns fun time! Let’s check that this is indeed right. I wrote a complete C++ program then compiled and ran it, and it worked:

    #include <iostream>
    
    double g(double x, double y[]) {return x * y[0];}
    double h(double x, ...) {return x;}
    
    double (*f(double (*)(double, double[]), double)) (double z, ...) {
      return h;
    }
    
    int main() {
      std::cout << f(g, 5.0)(2.0, 3.0, 8.0);
    }
    
  4. Both fields are present in the Derived object, but when accessed as a Derived object the name of the hidden (shadowed) field has to be qualified with the base class name, e.g. Base::b. It is also accessible when accessed through a Base variable (or a Base* variable) though, in which case you do not have to qualify it. Here’s the experiment to run:
    #include <cassert>
    #include <iostream>
    
    class Base {
    public:
      int a = 5;
      std::string b = "hello";
    };
    
    class Derived: public Base {
    public:
      float c = 2.0;
      int b = 34;
    };
    
    int main() {
      Derived d;
      assert(d.c == 2.0);
      assert(d.b == 34);
      assert(d.a == 5);
      assert(d.Base::b == "hello");
    
      // Can also get to the inherited fields by casting
      Base b = (Base)d;
      assert(b.a == 5);
      assert(b.b == "hello");
    
      // Can also get to the inherited fields by a "base class pointer"
      Base* p = &d;
      assert(p->a == 5);
      assert(p->b == "hello");
      std::cout << "ok\n";
    }
    
  5. This outputs:
    2
    5
    2
    
    due to static scoping. If C++ had used dynamic scoping we would have seen:
    5
    5
    2
    
  6. Signatures only:
    template <typename T>
    void shuffle_raw_array(T* a, int length);
    
    template <typename T, unsigned long length>
    void shuffle_standard_array(array<T, length>& a);
    

    If you are interested to see what the code looks like:

    #include <algorithm>
    
    template <typename T>
    void shuffle_raw_array(T* a, int length) {
      random_shuffle(a, a + length);
    }
    
    template <typename T, unsigned long length>
    void shuffle_standard_array(array<T, length>& a) {
      random_shuffle(a.begin(), a.end());
    }
    
  7. Word counting. This is slightly longer than it needs to be because I’m emphasizing readability. Sort of.

    wordcount.cpp
    #include <iostream>
    #include <vector>
    #include <map>
    
    using namespace std;
    
    auto make_counts_from_standard_input() {
      map<string, int> counts;
      for (string line; getline(cin, line);) {
        string word = "";
        for (char character: line) {
          char c = tolower(character);
          if (isalpha(c)) {
            word += c;
          } else if (word != "") {
            counts[word]++;
            word = "";
          }
        }
        if (word != "") {
          counts[word]++;
        }
      }
      return counts;
    }
    
    auto sort_counts(const map<string, int>& counts) {
      vector<pair<string, int>> v(counts.begin(), counts.end());
      sort(v.begin(), v.end(), [](auto x, auto y){return y.second < x.second;});
      return v;
    }
    
    void print_counts(const vector<pair<string, int>>& counts) {
      for (auto pair: counts) {
        cout << pair.first << ' ' << pair.second << '\n';
      }
    }
    
    int main() {
      print_counts(sort_counts(make_counts_from_standard_input()));
    }
    
  8. I think the easiest way to do this is to use a struct with overloaded function operator members:
    struct S {
      string words;
      string operator()() { return words; }
      S operator()(string word) { return {words + (words==""?"":" ") + word}; }
    } say;
    
  9. Here’s my queue class with moves but no copies:

    queue.h
    // A singly-linked queue class, built from scratch, with move semantics but no
    // copy semantics. The queue is represented as an object with pointers to the
    // head and tail nodes.
    
    #ifndef _SIMPLE_LINKED_QUEUE_H
    #define _SIMPLE_LINKED_QUEUE_H
    
    #include <stdexcept>
    #include <iostream>
    
    using namespace std;
    
    template<class T>
    class Queue {
    
    // A private local class for the nodes that are linked together.
    private:
      struct Node {
        T data;
        Node* next;
        ~Node() { delete next; }
      };
    
      Node* head = nullptr;
      Node* tail = nullptr;
      int size = 0;
    
    public:
      Queue() = default;
      Queue(const Queue&) = delete;
      Queue& operator=(const Queue&) = delete;
      Queue(Queue&& other) = default;
    
      // We have to define this ourselves (can't use default) because we have
      // a user-defined destructor.
      Queue& operator=(Queue&& other) {
        head = other.head;
        tail = other.tail;
        size = other.size;
        other.head = nullptr;
        other.tail = nullptr;
        return *this;
      }
    
      ~Queue() { delete head; }
    
      int get_size() { return size; }
    
      // To add an element we make a new node, attach it after the current tail
      // node, and update the queue's tail filed to point to this new node. One
      // special case is when the queue is initially empty: in this case the
      // new node becomes both the head and the tail.
      void enqueue(T x) {
        if (tail == nullptr) {
          head = tail = new Node { x, nullptr };
        } else {
          tail = tail->next = new Node { x, nullptr };
        }
        size++;
      }
    
      // Removing from a queue is taking the head element out. First of all if
      // there are no elements we have to throw an error because there is nothing
      // to remove. If there is, we save the head element to return later as
      // well as a pointer to the head node to delete later. We "detach" the node
      // by moving the queue's head pointer past it. Note the special case here!
      // If this was the last node, we have to update the queue's tail node field!
      // After all this, we can delete the node and return the value we saved up.
      T dequeue() {
        if (size == 0) {
          throw underflow_error("cannot remove from empty queue");
        }
        Node* nodeToDelete = head;
        T valueToReturn = head->data;
        head = head->next;
        if (head == nullptr) tail = nullptr;
        nodeToDelete->next = nullptr;
        delete nodeToDelete;
        size--;
        return valueToReturn;
      }
    
      friend ostream& operator<<(ostream& os, const Queue& queue) {
        os << "[";
        for (Node* node = queue.head; node != nullptr; node = node->next) {
          os << ' ' << node->data;
        }
        return os << " ]";
      }
    };
    
    #endif