sillyql / TableEntry.h
TableEntry.h
Raw
// Project Identifier: C0F4DFE8B340D81183C208F70F9D2D797908754D

// EECS 281 Project 3 SillyQL TableEntry class
// Copyright 2020, Regents of the University of Michigan

#pragma once

#include <utility>
#include <string>
#include <iosfwd>
#include <cassert>
#include <exception>


// The type contained within a TableEntry; you can use this also.
enum class EntryType : uint8_t { String, Double, Int, Bool };


class TableEntry {
  public:
  /* TableEntry can be constructed out of a string, an int, a double, or a bool.
   * Don't worry about the string&& constructor. This is a C++11 move
   * constructor. Understanding how these work is not required for this project.
   */
  explicit TableEntry(const char*);
  explicit TableEntry(const std::string&);
  explicit TableEntry(std::string&&);
  explicit TableEntry(double);
  explicit TableEntry(int);
  explicit TableEntry(bool);
  // You can also copy construct a TableEntry. Don't worry about TableEntry&&
  TableEntry(const TableEntry&);
  TableEntry(TableEntry&&) noexcept;
  // Please don't be assigning
  TableEntry& operator=(const TableEntry&) = delete;
  TableEntry& operator=(TableEntry&&) = delete;

  // IAMA destructor AMA
  ~TableEntry() noexcept;

  /* Comparisons with other TableEntry.
   * Don't try to compare TableEntry containing different types.
   * If you don't pass -DNDEBUG to g++ the compilation process (make debug with
   * the provided Makefile doesn't do this, but make release does), this will
   * cause an assertion to fail, which you can debug by using gdb (or your
   * favorite visual debugger) and looking at the stacktrace. Asserts are
   * removed if you compile with -DNDEBUG, and comparing different types causes
   * undefined behavior, which makes the autograder cry.
   */
  bool operator<(const TableEntry&) const noexcept;
  bool operator>(const TableEntry&) const noexcept;
  bool operator==(const TableEntry&) const noexcept;
  bool operator!=(const TableEntry&) const noexcept;

  /* Comparisons against internal type for convenience. These allow you to use
  * TableEntry as if they were the actul types stored in them. If you try to
  * compare a TableEntry with a type that is not the same as the internal type,
  * an assertion will trigger in the same way as for the above comparisons
  *
  * If you try to compare against a type other than the ones representable by
  * TableEntry, you will get a compiler error. That's what this gigantic
  * template/using is for. Don't worry about how it works.
  */
  template <typename T>
  // When c++14 becomes more used, change to decay_t and enable_if_t
  using Only_allow_for_table_types_t = typename std::enable_if<
      std::is_same<typename std::decay<T>::type, double>::value ||
      std::is_same<typename std::decay<T>::type, int>::value ||
      std::is_same<typename std::decay<T>::type, bool>::value ||
      std::is_same<typename std::decay<T>::type, std::string>::value>::type;
  template <typename T, typename = Only_allow_for_table_types_t<T>>
  bool operator<(const T&) const noexcept;
  template <typename T, typename = Only_allow_for_table_types_t<T>>
  bool operator>(const T&) const noexcept;
  template <typename T, typename = Only_allow_for_table_types_t<T>>
  bool operator==(const T&) const noexcept;
  template <typename T, typename = Only_allow_for_table_types_t<T>>
  bool operator!=(const T&) const noexcept;

  private:
  // Stop looking at my privates. You won't like what you see.
  // Skip down to after the end of the class for some other things

  EntryType tag;
  union {
    std::string data_string;
    double data_double;
    int data_int;
    bool data_bool;
  };

  friend struct std::hash<TableEntry>;
  friend std::ostream& operator<<(std::ostream&, const TableEntry&);

  /*
    fast_pass taken from facebook's fatal library.
    written by Marcelo Juchem <marcelo@fb.com>
    https://github.com/facebook/fatal/blob/master/fatal/type/fast_pass.h

    Modified slightly to apply here
    change to decay_t, add_const_t, condtional_t, and bool_constant as they
    become available.
    tl/dr if it's a scalar type, pass in as is, otherwise use a const&
  */
  template <typename T>
  using is_fast_pass = std::integral_constant<
      bool,
      std::is_scalar<typename std::decay<T>::type>::value>;
  template <typename T>
  using fast_pass = typename std::conditional<
      is_fast_pass<typename std::decay<T>::type>::value,
      typename std::decay<T>::type,
      typename std::add_lvalue_reference<typename std::add_const<
          typename std::decay<T>::type>::type>::type>::type;


  // as<T> operator to use the TableEntry as if it were type T. Only implemented
  // for the types allowed as TableEntry and returns a const& for string.
  template <typename T> fast_pass<T> as() const noexcept;

  // helper for comparisons with other TableEntry.
  // Change to template <template <typename> typename COMP> in c++17
  template <template <typename> class COMP>
  bool compare(const TableEntry&) const noexcept;

  // Helper to dedupe constructor code
  template <typename TT> void construct_from(TT&&);

}; // TableEntry

// This is so you don't have to pass a hash function to the unordered_map
// template. Now the unordered_map understands what to do with a TableEntry
// basically you can just do unordered_map<TableEntry, othertype> and the hashing
// just magically works
//
// Note: Don't normally put things in the standard namespace.
namespace std {
template <> struct hash<TableEntry> {
  size_t operator()(const TableEntry&) const noexcept;
};
}

// You can print TableEntry, it just prints the internal value.
// So TableEntry tt(11); cout << tt; prints "11" (without quotes)
std::ostream& operator<<(std::ostream&, const TableEntry&);

// These allow you to compare TableEntry with whatever types they represent in
// both orders (i.e. tt < 7 as well as 7 > tt).
template <typename T> bool operator<(const T& t, const TableEntry& tt) noexcept;
template <typename T> bool operator>(const T& t, const TableEntry& tt) noexcept;
template <typename T> bool operator==(const T& t, const TableEntry& tt) noexcept;
template <typename T> bool operator!=(const T& t, const TableEntry& tt) noexcept;


////////////////////////////////////////////////////////////////////////////////
/*        Implementations here. Stop reading if you value your sanity.        */
/* But seriously, you don't have to look at or understand anything below here */
////////////////////////////////////////////////////////////////////////////////


template <typename T> bool operator<(const T& t, const TableEntry& tt) noexcept {
  return tt > t;
}
template <typename T> bool operator>(const T& t, const TableEntry& tt) noexcept {
  return tt < t;
}
template <typename T>
bool operator==(const T& t, const TableEntry& tt) noexcept {
  return tt == t;
}
template <typename T>
bool operator!=(const T& t, const TableEntry& tt) noexcept {
  return tt != t;
}

template <typename T, typename>
bool TableEntry::operator<(const T& t) const noexcept {
  return as<T>() < t;
}
template <typename T, typename>
bool TableEntry::operator>(const T& t) const noexcept {
  return as<T>() > t;
}
template <typename T, typename>
bool TableEntry::operator==(const T& t) const noexcept {
  return as<T>() == t;
}
template <typename T, typename>
bool TableEntry::operator!=(const T& t) const noexcept {
  return as<T>() != t;
}

// Helper to dedupe comparison operators
template <template <typename> class COMP>
bool TableEntry::compare(const TableEntry& other) const noexcept {
  assert(tag == other.tag &&
         "tried to compare TableEntry containing different types");
  switch (tag) {
  case EntryType::String:
    return COMP<std::string>{}(data_string, other.data_string);
  case EntryType::Double:
    return COMP<double>{}(data_double, other.data_double);
  case EntryType::Int:
    return COMP<int>{}(data_int, other.data_int);
  case EntryType::Bool:
    return COMP<bool>{}(data_bool, other.data_bool);
  }
  std::terminate();
}

// Helper to dedupe constructor code
template <typename TT> void TableEntry::construct_from(TT&& other) {
  switch (tag) {
  case EntryType::String:
    // this is a hack bc a const&& still can't be moved from
    new (&data_string) std::string{std::move(other.data_string)};
    break;
  case EntryType::Double:
    data_double = other.data_double;
    break;
  case EntryType::Int:
    data_int = other.data_int;
    break;
  case EntryType::Bool:
    data_bool = other.data_bool;
    break;
  }
}