diff -r 47494f91fae6 libinterp/corefcn/stack-frame.h
--- a/libinterp/corefcn/stack-frame.h	Sun Feb 26 10:26:20 2023 -0500
+++ b/libinterp/corefcn/stack-frame.h	Sun Feb 26 11:06:51 2023 -0500
@@ -461,6 +461,15 @@ public:
   }
 
   void assign (octave_value::assign_op op, const symbol_record& sym,
+               const octave_value& rhs)
+  {
+    if (op == octave_value::op_asn_eq)
+      assign (sym, rhs);
+    else
+      varref (sym).assign (op, rhs);
+  }
+
+  void assign (octave_value::assign_op op, const symbol_record& sym,
                const std::string& type,
                const std::list<octave_value_list>& idx,
                const octave_value& rhs)
@@ -477,20 +486,31 @@ public:
   }
 
   void non_const_unary_op (octave_value::unary_op op,
+                           const symbol_record& sym)
+  {
+    varref (sym).non_const_unary_op (op);
+  }
+
+  void non_const_unary_op (octave_value::unary_op op,
                            const symbol_record& sym,
                            const std::string& type,
                            const std::list<octave_value_list>& idx)
   {
     if (idx.empty ())
-      varref (sym).non_const_unary_op (op);
+      non_const_unary_op (op, sym);
     else
       varref (sym).non_const_unary_op (op, type, idx);
   }
 
+  octave_value value (const symbol_record& sym) const
+  {
+    return varval (sym);
+  }
+
   octave_value value (const symbol_record& sym, const std::string& type,
                       const std::list<octave_value_list>& idx) const
   {
-    octave_value retval = varval (sym);
+    octave_value retval = value (sym);
 
     if (! idx.empty ())
       {
diff -r 47494f91fae6 libinterp/parse-tree/oct-lvalue.cc
--- a/libinterp/parse-tree/oct-lvalue.cc	Sun Feb 26 10:26:20 2023 -0500
+++ b/libinterp/parse-tree/oct-lvalue.cc	Sun Feb 26 11:06:51 2023 -0500
@@ -37,29 +37,10 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
-bool octave_lvalue::is_defined () const
-{
-  return ! is_black_hole () && m_frame->is_defined (m_sym);
-}
-
-bool octave_lvalue::is_undefined () const
-{
-  return ! is_defined ();
-}
+// For const empty reference returns.
+const std::string octave_lvalue_base::m_empty_string = "";
 
-void octave_lvalue::define (const octave_value& v)
-{
-  m_frame->assign (m_sym, v);
-}
-
-void octave_lvalue::assign (octave_value::assign_op op,
-                            const octave_value& rhs)
-{
-  if (! is_black_hole ())
-    m_frame->assign (op, m_sym, m_type, m_idx, rhs);
-}
-
-octave_idx_type octave_lvalue::numel () const
+octave_idx_type octave_lvalue_idx::numel () const
 {
   // Return 1 if there is no index because without an index there
   // should be no way to have a cs-list here.  Cs-lists may be passed
@@ -168,17 +149,7 @@ octave_idx_type octave_lvalue::numel () 
     }
 }
 
-void octave_lvalue::set_index (const std::string& t,
-                               const std::list<octave_value_list>& i)
-{
-  if (! m_idx.empty ())
-    error ("invalid index expression in assignment");
-
-  m_type = t;
-  m_idx = i;
-}
-
-bool octave_lvalue::index_is_empty () const
+bool octave_lvalue_idx::index_is_empty () const
 {
   bool retval = false;
 
@@ -192,7 +163,7 @@ bool octave_lvalue::index_is_empty () co
   return retval;
 }
 
-bool octave_lvalue::index_is_colon () const
+bool octave_lvalue_idx::index_is_colon () const
 {
   bool retval = false;
 
@@ -206,21 +177,11 @@ bool octave_lvalue::index_is_colon () co
   return retval;
 }
 
-void octave_lvalue::unary_op (octave_value::unary_op op)
-{
-  if (! is_black_hole ())
-    m_frame->non_const_unary_op (op, m_sym, m_type, m_idx);
-}
-
-octave_value octave_lvalue::value () const
-{
-  return (is_black_hole ()
-          ? octave_value () : m_frame->value (m_sym, m_type, m_idx));
-}
 
 octave_value
-octave_lvalue::eval_for_numel (const std::string& type,
-                               const std::list<octave_value_list>& idx) const
+octave_lvalue_idx::
+eval_for_numel (const std::string& type,
+                const std::list<octave_value_list>& idx) const
 {
   octave_value retval;
 
diff -r 47494f91fae6 libinterp/parse-tree/oct-lvalue.h
--- a/libinterp/parse-tree/oct-lvalue.h	Sun Feb 26 10:26:20 2023 -0500
+++ b/libinterp/parse-tree/oct-lvalue.h	Sun Feb 26 11:06:51 2023 -0500
@@ -36,48 +36,125 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
-class octave_lvalue
+class octave_lvalue_base
 {
 public:
+  friend class octave_lvalue;
 
-  octave_lvalue (const symbol_record& sr,
-                 const std::shared_ptr<stack_frame>& frame)
-    : m_sym (sr), m_frame (frame), m_black_hole (false),
-      m_type (), m_idx ()
-  { }
+  octave_lvalue_base (const symbol_record& sr,
+                      const std::shared_ptr<stack_frame>& frame)
+    : m_sym (sr), m_frame (frame) { }
+
+  octave_lvalue_base (symbol_record &&sr,
+                      std::shared_ptr<stack_frame> &&frame)
+    : m_sym (sr), m_frame (frame) { }
+
+  virtual ~octave_lvalue_base () = default;
+
+  virtual bool is_black_hole (void) const { return false; }
 
-  OCTAVE_DEFAULT_CONSTRUCT_COPY_MOVE_DELETE (octave_lvalue)
+  virtual bool is_defined (void) const
+  {
+    return m_frame->is_defined (m_sym);
+  }
+
+  void define (const octave_value& v)
+  {
+    // ??? Does this make sense for blackhole?
+    m_frame->assign (m_sym, v);
+  }
+
+  virtual void assign (octave_value::assign_op op,
+                       const octave_value& rhs) = 0;
 
-  bool is_black_hole () const { return m_black_hole; }
+  virtual octave_idx_type numel (void) const { return 1; }
+
+  virtual octave_value value (void) const = 0;
+  virtual void unary_op (octave_value::unary_op op) = 0;
 
-  void mark_black_hole () { m_black_hole = true; }
+  virtual octave_lvalue_base *
+  set_index (const std::string&,
+             const std::list<octave_value_list>&) = 0;
+
+  virtual octave_lvalue_base * clone () const = 0;
+
+  // idx has specializations of these index related functions
 
-  bool is_defined () const;
+  virtual void clear_index (void) { };
+  virtual const std::string & index_type (void) const
+  {
+    return m_empty_string;
+  }
+  // Scalars are never empty.
+  virtual bool index_is_empty (void) const { return false; }
+  virtual bool index_is_colon (void) const { return false; }
 
-  bool is_undefined () const;
+  symbol_record m_sym;
 
-  bool isstruct () const { return value().isstruct (); }
+  std::shared_ptr<stack_frame> m_frame;
+
+  static const std::string m_empty_string;
+};
 
-  void define (const octave_value& v);
-
-  void assign (octave_value::assign_op, const octave_value&);
+class octave_lvalue_idx : public octave_lvalue_base
+{
+public:
+  octave_lvalue_idx (const symbol_record& sr,
+                     const std::shared_ptr<stack_frame>& frame,
+                     const std::string &type,
+                     const std::list<octave_value_list> &idx)
+    : octave_lvalue_base (sr, frame), m_type (type), m_idx (idx) { }
 
-  octave_idx_type numel () const;
+  octave_lvalue_idx (symbol_record &&sr,
+                     std::shared_ptr<stack_frame> &&frame,
+                     std::string &&type,
+                     std::list<octave_value_list> &&idx)
+    : octave_lvalue_base (sr, frame), m_type (type), m_idx (idx) { }
+
+  ~octave_lvalue_idx () {};
 
-  void set_index (const std::string& t,
-                  const std::list<octave_value_list>& i);
+  void assign (octave_value::assign_op op,  const octave_value& rhs)
+  {
+    m_frame->assign (op, m_sym, m_type, m_idx, rhs);
+  }
+
+  octave_idx_type numel (void) const;
+
+  octave_value value (void) const
+  {
+    return m_frame->value (m_sym, m_type, m_idx);
+  }
 
-  void clear_index () { m_type = ""; m_idx.clear (); }
+  octave_lvalue_base *
+  set_index (const std::string& t,
+             const std::list<octave_value_list>& i) final
+  {
+    if (! m_idx.empty ())
+      error ("invalid index expression in assignment");
 
-  std::string index_type () const { return m_type; }
+    m_type = t;
+    m_idx = i;
 
-  bool index_is_empty () const;
+    return this;
+  }
+
+  void clear_index (void) final { m_type = ""; m_idx.clear (); }
 
-  bool index_is_colon () const;
+  const std::string & index_type (void) const final { return m_type; }
+
+  bool index_is_empty (void) const final;
+
+  bool index_is_colon (void) const final;
 
-  void unary_op (octave_value::unary_op op);
+  void unary_op (octave_value::unary_op op) final
+  {
+    m_frame->non_const_unary_op (op, m_sym, m_type, m_idx);
+  }
 
-  octave_value value () const;
+  octave_lvalue_base * clone () const
+  {
+    return new octave_lvalue_idx (m_sym, m_frame, m_type, m_idx);
+  }
 
 private:
 
@@ -85,15 +162,192 @@ private:
   eval_for_numel (const std::string& type,
                   const std::list<octave_value_list>& idx) const;
 
-  symbol_record m_sym;
-
-  std::shared_ptr<stack_frame> m_frame;
-
-  bool m_black_hole;
-
   std::string m_type;
 
   std::list<octave_value_list> m_idx;
+
+  octave_idx_type m_nel;
+};
+
+class octave_lvalue_id : public octave_lvalue_base
+{
+public:
+  octave_lvalue_id (const symbol_record& sr,
+                    const std::shared_ptr<stack_frame>& frame)
+    : octave_lvalue_base (sr, frame) { }
+
+  octave_lvalue_id (symbol_record &&sr,
+                    std::shared_ptr<stack_frame> &&frame)
+    : octave_lvalue_base (sr, frame) { }
+
+  ~octave_lvalue_id () {};
+
+  void assign (octave_value::assign_op op,  const octave_value& rhs)
+  {
+    m_frame->assign (op, m_sym, rhs);
+  }
+
+  octave_value value (void) const
+  {
+    return m_frame->value (m_sym);
+  }
+
+  void unary_op (octave_value::unary_op op)
+  {
+    m_frame->non_const_unary_op (op, m_sym);
+  }
+
+  octave_lvalue_base * clone () const
+  {
+    return new octave_lvalue_id (m_sym, m_frame);
+  }
+
+  // Makes the octave_lvalue_id object invalid due to std::move
+  // of members
+  octave_lvalue_base *
+  set_index (const std::string &type,
+             const std::list<octave_value_list>& idx)
+  {
+    return new octave_lvalue_idx (std::move (m_sym),
+                                  std::move (m_frame),
+                                  type, idx);
+  }
+};
+
+class octave_lvalue_blackhole : public octave_lvalue_base
+{
+public:
+  octave_lvalue_blackhole (const symbol_record& sr,
+                           const std::shared_ptr<stack_frame>& frame)
+    : octave_lvalue_base (sr, frame) { }
+
+  ~octave_lvalue_blackhole () {};
+
+  bool is_defined (void) const { return false; }
+
+  void assign (octave_value::assign_op , const octave_value& ) { }
+
+  octave_value value (void) const { return octave_value (); }
+
+  void unary_op (octave_value::unary_op) { }
+
+  octave_lvalue_base * clone () const
+  {
+    return new octave_lvalue_blackhole (m_sym, m_frame);
+  }
+
+  bool is_black_hole (void) const { return true; }
+
+  octave_lvalue_base *
+  set_index (const std::string &,
+             const std::list<octave_value_list>&)
+  {
+    error ("can't set index on black hole");
+    return nullptr; // Unreachable
+  }
+};
+
+class octave_lvalue
+{
+public:
+
+  static octave_lvalue
+  make_lvalue_idx (const symbol_record& sr,
+                   const std::shared_ptr<stack_frame>& frame,
+                   const std::string &type,
+                   const std::list<octave_value_list> &idx)
+  {
+    auto *m_rep = new octave_lvalue_idx (sr, frame, type, idx);
+    return octave_lvalue (m_rep);
+  }
+
+  static octave_lvalue
+  make_lvalue_id (const symbol_record& sr,
+                  const std::shared_ptr<stack_frame>& frame)
+  {
+    auto *m_rep = new octave_lvalue_id (sr, frame);
+    return octave_lvalue (m_rep);
+  }
+
+  static octave_lvalue
+  make_lvalue_blackhole (const symbol_record& sr,
+                         const std::shared_ptr<stack_frame>& frame)
+  {
+    auto *m_rep = new octave_lvalue_blackhole (sr, frame);
+    return octave_lvalue (m_rep);
+  }
+
+  // Makes an identifier lvalue
+  octave_lvalue (const symbol_record& sr,
+                 const std::shared_ptr<stack_frame>& frame)
+  {
+    m_rep = new octave_lvalue_id (sr, frame);
+  }
+
+  octave_lvalue (const octave_lvalue& olv)
+  {
+    m_rep = olv.m_rep->clone ();
+  }
+
+  octave_lvalue (octave_lvalue&& olv)
+  {
+    m_rep = olv.m_rep;
+    olv.m_rep = nullptr;
+  }
+
+  ~octave_lvalue ()
+  {
+    delete m_rep;
+  };
+
+  bool is_black_hole () const { return m_rep->is_black_hole(); }
+
+  bool is_defined () const { return m_rep->is_defined (); }
+
+  bool is_undefined () const { return ! m_rep->is_defined (); }
+
+  bool isstruct () const { return value().isstruct (); }
+
+  void define (const octave_value& v) { m_rep->define (v); }
+
+  void assign (octave_value::assign_op op, const octave_value &ov)
+  {
+    m_rep->assign (op, ov);
+  }
+
+  octave_idx_type numel () const { return m_rep->numel (); }
+
+  void set_index (const std::string& t,
+                  const std::list<octave_value_list>& i)
+  {
+    // id will change type to idx here, black_hole will error
+    octave_lvalue_base * tmp = m_rep;
+    m_rep = m_rep->set_index (t, i);
+    if (m_rep != tmp)
+      delete tmp;
+  }
+
+  void clear_index () { m_rep->clear_index(); }
+
+  const std::string & index_type () const { return m_rep->index_type (); }
+
+  bool index_is_empty () const { return m_rep->index_is_empty (); }
+
+  bool index_is_colon () const { return m_rep->index_is_colon (); }
+
+  void unary_op (octave_value::unary_op op)
+  {
+    m_rep->unary_op(op);
+  }
+
+  octave_value value () const { return m_rep->value ();}
+
+private:
+
+  octave_lvalue_base *m_rep;
+
+  // Only for builder functions
+  octave_lvalue (octave_lvalue_base *rep) : m_rep (rep) { };
 };
 
 OCTAVE_END_NAMESPACE(octave)
diff -r 47494f91fae6 libinterp/parse-tree/pt-id.cc
--- a/libinterp/parse-tree/pt-id.cc	Sun Feb 26 10:26:20 2023 -0500
+++ b/libinterp/parse-tree/pt-id.cc	Sun Feb 26 11:06:51 2023 -0500
@@ -155,11 +155,9 @@ tree_identifier::evaluate_n (tree_evalua
 octave_lvalue
 tree_black_hole::lvalue (tree_evaluator& tw)
 {
-  octave_lvalue retval (m_sym, tw.get_current_stack_frame ());
+  return octave_lvalue::make_lvalue_blackhole (m_sym,
+                                               tw.get_current_stack_frame ());
 
-  retval.mark_black_hole ();
-
-  return retval;
 }
 
 OCTAVE_END_NAMESPACE(octave)
