namespace factor {

template <typename Type> cell tag(Type* value) {
  return RETAG(value, Type::type_number);
}

inline static cell tag_dynamic(object* value) {
  return RETAG(value, value->type());
}

template <typename Type> struct tagged {
  cell value_;

  cell type() const { return TAG(value_); }

  bool type_p(cell type_) const { return type() == type_; }

  bool type_p() const {
    if (Type::type_number == TYPE_COUNT)
      return true;
    else
      return type_p(Type::type_number);
  }

  cell value() const {
    FACTOR_ASSERT(type_p());
    return value_;
  }

  Type* untagged() const {
    FACTOR_ASSERT(type_p());
    return (Type*)(UNTAG(value_));
  }

  Type* untag_check(factor_vm* parent) const {
    if (!type_p())
      parent->type_error(Type::type_number, value_);
    return untagged();
  }

  explicit tagged(cell tagged) : value_(tagged) {}
  explicit tagged(Type* untagged) : value_(factor::tag(untagged)) {}

  Type* operator->() const { return untagged(); }
  cell* operator&() const { return &value_; }

  const tagged<Type>& operator=(const Type* x) {
    value_ = tag(x);
    return *this;
  }
  const tagged<Type>& operator=(const cell& x) {
    value_ = x;
    return *this;
  }

  bool operator==(const tagged<Type>& x) { return value_ == x.value_; }
  bool operator!=(const tagged<Type>& x) { return value_ != x.value_; }

  template <typename NewType> tagged<NewType> as() {
    return tagged<NewType>(value_);
  }
};

template <typename Type> Type* factor_vm::untag_check(cell value) {
  return tagged<Type>(value).untag_check(this);
}

template <typename Type> Type* untag(cell value) {
  return tagged<Type>(value).untagged();
}

}
