Repository: biojppm/rapidyaml (language: python)

Issue:
const on return types
While looking quickly at the code I often see things like:

```cpp
NodeRef operator[] (csubstr k)
{
    // [...]
    NodeRef r = ...;
    return r;
}

NodeRef const operator[] (csubstr k) const
{
    // [...]
    NodeRef const r = ...;
    return r;
}
```
I might be missing something but I see two problems with this pattern:
 1. having a const value as the return type makes no sense since the returned value can be assigned to a non-const one by making a copy
 2. returning a const value prevents moves which in turn prevents NRVO

See the [relevant clang-tidy check](https://releases.llvm.org/11.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability-const-return-type.html)

If you want constness propagated to returned values it must be encoded in the type. For instance you could have a `NodeConstRef` with only the const-qualified member functions of `NodeRef` and use that as a return type for such const-qualified functions.


Discussion / hints:
Thanks for the note. Your remarks are correct, and something that I've been meaning to address.

Fix patch:
diff --git a/README.md b/README.md
index 5c8f13c87..0ce08b3e8 100644
--- a/README.md
+++ b/README.md
@@ -250,7 +250,7 @@ eg the [`add_subdirectory()` sample](samples/add_subdirectory/).
 //   - reuse an existing tree (advised)
 //   - reuse an existing parser (advised)
 char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
-ryml::Tree tree = ryml::parse_in_place(ryml::substr(yml_buf));
+ryml::Tree tree = ryml::parse_in_place(yml_buf);
 
 // Note: it will always be significantly faster to use mutable
 // buffers and reuse tree+parser.
@@ -276,14 +276,14 @@ CHECK(tree.is_seq(bar_id));  // ... and receive the subject index
 
 // The node API is a lightweight abstraction sitting on top of the
 // index API, but offering a much more convenient interaction:
-ryml::NodeRef root = tree.rootref();
-ryml::NodeRef bar = tree["bar"];
+ryml::ConstNodeRef root = tree.rootref();
+ryml::ConstNodeRef bar = tree["bar"];
 CHECK(root.is_map());
 CHECK(bar.is_seq());
-// NodeRef is a lightweight handle to the tree and associated id:
-CHECK(root.tree() == &tree); // NodeRef points at its tree, WITHOUT refcount
-CHECK(root.id() == root_id); // NodeRef's id is the index of the node
-CHECK(bar.id() == bar_id);   // NodeRef's id is the index of the node
+// A node ref is a lightweight handle to the tree and associated id:
+CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount
+CHECK(root.id() == root_id); // a node ref's id is the index of the node
+CHECK(bar.id() == bar_id);   // a node ref's id is the index of the node
 
 // The node API translates very cleanly to the index API, so most
 // of the code examples below are using the node API.
@@ -297,14 +297,14 @@ CHECK(bar.id() == bar_id);   // NodeRef's id is the index of the node
 //------------------------------------------------------------------
 // To read the parsed tree
 
-// Node::operator[] does a lookup, is O(num_children[node]).
-// maps use string keys, seqs use integral keys.
+// ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
 CHECK(tree["foo"].is_keyval());
 CHECK(tree["foo"].key() == "foo");
 CHECK(tree["foo"].val() == "1");
 CHECK(tree["bar"].is_seq());
 CHECK(tree["bar"].has_key());
 CHECK(tree["bar"].key() == "bar");
+// maps use string keys, seqs use integral keys:
 CHECK(tree["bar"][0].val() == "2");
 CHECK(tree["bar"][1].val() == "3");
 CHECK(tree["john"].val() == "doe");
@@ -314,12 +314,11 @@ CHECK(tree["john"].val() == "doe");
 CHECK(tree[0].id() == tree["foo"].id());
 CHECK(tree[1].id() == tree["bar"].id());
 CHECK(tree[2].id() == tree["john"].id());
-// Tree::operator[](int) searches a root child by its position.
+// Tree::operator[](int) searches a ***root*** child by its position.
 CHECK(tree[0].id() == tree["foo"].id());  // 0: first child of root
 CHECK(tree[1].id() == tree["bar"].id());  // 1: first child of root
 CHECK(tree[2].id() == tree["john"].id()); // 2: first child of root
-// NodeRef::operator[](int) searches a node child by its position
-// on __the node__'s children list:
+// NodeRef::operator[](int) searches a ***node*** child by its position:
 CHECK(bar[0].val() == "2"); // 0 means first child of bar
 CHECK(bar[1].val() == "3"); // 1 means second child of bar
 // NodeRef::operator[](string):
@@ -337,29 +336,36 @@ CHECK(root["foo"].id() == root[0].id());
 CHECK(root["bar"].id() == root[1].id());
 CHECK(root["john"].id() == root[2].id());
 
-// Please note that since a ryml tree uses indexed linked lists for storing
-// children, the complexity of `Tree::operator[csubstr]` and
-// `Tree::operator[size_t]` is linear on the number of root children. If you use
-// it with a large tree where the root has many children, you may get a
-// performance hit. To avoid this hit, you can create your own accelerator
-// structure. For example, before doing a lookup, do a single traverse at the
-// root level to fill an `std::map<csubstr,size_t>` mapping key names to node
-// indices; with a node index, a lookup (via `Tree::get()`) is O(1), so this way
-// you can get O(log n) lookup from a key.
+// IMPORTANT. The ryml tree uses indexed linked lists for storing
+// children, so the complexity of `Tree::operator[csubstr]` and
+// `Tree::operator[size_t]` is linear on the number of root
+// children. If you use `Tree::operator[]` with a large tree where
+// the root has many children, you will see a performance hit.
 //
-// As for `NodeRef`, the difference from `NodeRef::operator[]`
-// to `Tree::operator[]` is that the latter refers to the root node, whereas
-// the former can be invoked on any node. But the lookup process is the same for
-// both and their algorithmic complexity is the same: they are both linear in
-// the number of direct children; but depending on the data, that number may
-// be very different from one to another.
+// To avoid this hit, you can create your own accelerator
+// structure. For example, before doing a lookup, do a single
+// traverse at the root level to fill an `map<csubstr,size_t>`
+// mapping key names to node indices; with a node index, a lookup
+// (via `Tree::get()`) is O(1), so this way you can get O(log n)
+// lookup from a key. (But please do not use `std::map` if you
+// care about performance; use something else like a flat map or
+// sorted vector).
+//
+// As for node refs, the difference from `NodeRef::operator[]` and
+// `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
+// latter refers to the root node, whereas the former are invoked
+// on their target node. But the lookup process works the same for
+// both and their algorithmic complexity is the same: they are
+// both linear in the number of direct children. But of course,
+// depending on the data, that number may be very different from
+// one to another.
 
 //------------------------------------------------------------------
 // Hierarchy:
 
 {
-    ryml::NodeRef foo = root.first_child();
-    ryml::NodeRef john = root.last_child();
+    ryml::ConstNodeRef foo = root.first_child();
+    ryml::ConstNodeRef john = root.last_child();
     CHECK(tree.size() == 6); // O(1) number of nodes in the tree
     CHECK(root.num_children() == 3); // O(num_children[root])
     CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
@@ -384,13 +390,13 @@ CHECK(root["john"].id() == root[2].id());
     // iterate children using the high-level node API:
     {
         size_t count = 0;
-        for(ryml::NodeRef const& child : root.children())
+        for(ryml::ConstNodeRef const& child : root.children())
             CHECK(child.key() == expected_keys[count++]);
     }
     // iterate siblings using the high-level node API:
     {
         size_t count = 0;
-        for(ryml::NodeRef const& child : root["foo"].siblings())
+        for(ryml::ConstNodeRef const& child : root["foo"].siblings())
             CHECK(child.key() == expected_keys[count++]);
     }
     // iterate children using the lower-level tree index API:
@@ -440,13 +446,19 @@ CHECK(!tree["bar"][1].has_key());       // belongs to a seq, so no key
 //------------------------------------------------------------------
 // Modifying existing nodes: operator<< vs operator=
 
+// As implied by its name, ConstNodeRef is a reference to a const
+// node. It can be used to read from the node, but not write to it
+// or modify the hierarchy of the node. If any modification is
+// desired then a NodeRef must be used instead:
+ryml::NodeRef wroot = tree.rootref();
+
 // operator= assigns an existing string to the receiving node.
 // This pointer will be in effect until the tree goes out of scope
 // so beware to only assign from strings outliving the tree.
-root["foo"] = "says you";
-root["bar"][0] = "-2";
-root["bar"][1] = "-3";
-root["john"] = "ron";
+wroot["foo"] = "says you";
+wroot["bar"][0] = "-2";
+wroot["bar"][1] = "-3";
+wroot["john"] = "ron";
 // Now the tree is _pointing_ at the memory of the strings above.
 // That is OK because those are static strings and will outlive
 // the tree.
@@ -465,10 +477,10 @@ CHECK(root["john"].val() == "ron");
 // assigns the serialized string to the receiving node. This avoids
 // constraints with the lifetime, since the arena lives with the tree.
 CHECK(tree.arena().empty());
-root["foo"] << "says who";  // requires to_chars(). see serialization samples below.
-root["bar"][0] << 20;
-root["bar"][1] << 30;
-root["john"] << "deere";
+wroot["foo"] << "says who";  // requires to_chars(). see serialization samples below.
+wroot["bar"][0] << 20;
+wroot["bar"][1] << 30;
+wroot["john"] << "deere";
 CHECK(root["foo"].val() == "says who");
 CHECK(root["bar"][0].val() == "20");
 CHECK(root["bar"][1].val() == "30");
@@ -478,7 +490,7 @@ CHECK(tree.arena() == "says who2030deere"); // the result of serializations to t
 {
     std::string ok("in_scope");
     // root["john"] = ryml::to_csubstr(ok); // don't, will dangle
-    root["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
+    wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
 }
 CHECK(root["john"] == "in_scope"); // OK!
 CHECK(tree.arena() == "says who2030deerein_scope"); // the result of serializations to the tree arena
@@ -489,40 +501,41 @@ CHECK(tree.arena() == "says who2030deerein_scope"); // the result of serializati
 
 // adding a keyval node to a map:
 CHECK(root.num_children() == 3);
-root["newkeyval"] = "shiny and new"; // using these strings
-root.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization
+wroot["newkeyval"] = "shiny and new"; // using these strings
+wroot.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization
 CHECK(root.num_children() == 5);
 CHECK(root["newkeyval"].key() == "newkeyval");
 CHECK(root["newkeyval"].val() == "shiny and new");
 CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
 CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
-CHECK( ! root["newkeyval"].key().is_sub(tree.arena())); // it's using directly the static string above
-CHECK( ! root["newkeyval"].val().is_sub(tree.arena())); // it's using directly the static string above
-CHECK(   root["newkeyval (serialized)"].key().is_sub(tree.arena())); // it's using a serialization of the string above
-CHECK(   root["newkeyval (serialized)"].val().is_sub(tree.arena())); // it's using a serialization of the string above
+CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
+CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
+CHECK(   tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
+CHECK(   tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
 // adding a val node to a seq:
 CHECK(root["bar"].num_children() == 2);
-root["bar"][2] = "oh so nice";
-root["bar"][3] << "oh so nice (serialized)";
+wroot["bar"][2] = "oh so nice";
+wroot["bar"][3] << "oh so nice (serialized)";
 CHECK(root["bar"].num_children() == 4);
 CHECK(root["bar"][2].val() == "oh so nice");
 CHECK(root["bar"][3].val() == "oh so nice (serialized)");
 // adding a seq node:
 CHECK(root.num_children() == 5);
-root["newseq"] |= ryml::SEQ;
-root.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
+wroot["newseq"] |= ryml::SEQ;
+wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
 CHECK(root.num_children() == 7);
 CHECK(root["newseq"].num_children() == 0);
 CHECK(root["newseq (serialized)"].num_children() == 0);
 // adding a map node:
 CHECK(root.num_children() == 7);
-root["newmap"] |= ryml::MAP;
-root.append_child() << ryml::key("newmap (serialized)") |= ryml::SEQ;
+wroot["newmap"] |= ryml::MAP;
+wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::SEQ;
 CHECK(root.num_children() == 9);
 CHECK(root["newmap"].num_children() == 0);
 CHECK(root["newmap (serialized)"].num_children() == 0);
-// operator[] does not mutate the tree until the returned node is
-// written to.
+//
+// When the tree is mutable, operator[] does not mutate the tree
+// until the returned node is written to.
 //
 // Until such time, the NodeRef object keeps in itself the required
 // information to write to the proper place in the tree. This is
@@ -530,25 +543,44 @@ CHECK(root["newmap (serialized)"].num_children() == 0);
 //
 // This means that passing a key/index which does not exist will
 // not mutate the tree, but will instead store (in the node) the
-// proper place of the tree to do so if and when it is required.
+// proper place of the tree to be able to do so, if and when it is
+// required.
 //
 // This is a significant difference from eg, the behavior of
 // std::map, which mutates the map immediately within the call to
 // operator[].
-CHECK(!root.has_child("I am nobody"));
-ryml::NodeRef nobody = root["I am nobody"];
-CHECK(nobody.valid());   // points at the tree, and a specific place in the tree
-CHECK(nobody.is_seed()); // ... but nothing is there yet.
-CHECK(!root.has_child("I am nobody")); // same as above
-ryml::NodeRef somebody = root["I am somebody"];
-CHECK(!root.has_child("I am somebody")); // same as above
-CHECK(somebody.valid());
-CHECK(somebody.is_seed()); // same as above
-somebody = "indeed";  // this will commit to the tree, mutating at the proper place
-CHECK(somebody.valid());
-CHECK(!somebody.is_seed()); // now the tree has this node, and it is no longer a seed
-CHECK(root.has_child("I am somebody"));
-CHECK(root["I am somebody"].val() == "indeed");
+//
+// All of the points above apply only if the tree is mutable. If
+// the tree is const, then a NodeRef cannot be obtained from it;
+// only a ConstNodeRef, which can never be used to mutate the
+// tree.
+CHECK(!root.has_child("I am not nothing"));
+ryml::NodeRef nothing = wroot["I am nothing"];
+CHECK(nothing.valid());   // points at the tree, and a specific place in the tree
+CHECK(nothing.is_seed()); // ... but nothing is there yet.
+CHECK(!root.has_child("I am nothing")); // same as above
+ryml::NodeRef something = wroot["I am something"];
+ryml::ConstNodeRef constsomething = wroot["I am something"];
+CHECK(!root.has_child("I am something")); // same as above
+CHECK(something.valid());
+CHECK(something.is_seed()); // same as above
+CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef
+                                // cannot be used to mutate a
+                                // tree, it is only valid() if it
+                                // is pointing at an existing
+                                // node.
+something = "indeed";  // this will commit to the tree, mutating at the proper place
+CHECK(root.has_child("I am something"));
+CHECK(root["I am something"].val() == "indeed");
+CHECK(something.valid());
+CHECK(!something.is_seed()); // now the tree has this node, so the
+                             // ref is no longer a seed
+// now the constref is also valid (but it needs to be reassigned):
+ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
+CHECK(constsomethingnew.valid());
+// note that the old constref is now stale, because it only keeps
+// the state at creation:
+CHECK(!constsomething.valid());
 
 
 //------------------------------------------------------------------
@@ -579,7 +611,7 @@ newseq: []
 newseq (serialized): []
 newmap: {}
 newmap (serialized): []
-I am somebody: indeed
+I am something: indeed
 )";
 CHECK(buf_result == expected_result);
 CHECK(str_result == expected_result);
@@ -587,6 +619,38 @@ CHECK(stream_result == expected_result);
 // There are many possibilities to emit to buffer;
 // please look at the emit sample functions below.
 
+//------------------------------------------------------------------
+// ConstNodeRef vs NodeRef
+
+ryml::NodeRef noderef = tree["bar"][0];
+ryml::ConstNodeRef constnoderef = tree["bar"][0];
+
+// ConstNodeRef cannot be used to mutate the tree, but a NodeRef can:
+//constnoderef = "21";  // compile error
+//constnoderef << "22"; // compile error
+noderef = "21";         // ok, can assign because it's not const
+CHECK(tree["bar"][0].val() == "21");
+noderef << "22";        // ok, can serialize and assign because it's not const
+CHECK(tree["bar"][0].val() == "22");
+
+// it is not possible to obtain a NodeRef from a ConstNodeRef:
+// noderef = constnoderef; // compile error
+
+// it is always possible to obtain a ConstNodeRef from a NodeRef:
+constnoderef = noderef;    // ok can assign const <- nonconst
+
+// If a tree is const, then only ConstNodeRef's can be
+// obtained from that tree:
+ryml::Tree const& consttree = tree;
+//noderef = consttree["bar"][0];    // compile error
+noderef = tree["bar"][0];           // ok
+constnoderef = consttree["bar"][0]; // ok
+
+// ConstNodeRef and NodeRef can be compared for equality.
+// Equality means they point at the same node.
+CHECK(constnoderef == noderef);
+CHECK(!(constnoderef != noderef));
+
 //------------------------------------------------------------------
 // Dealing with UTF8
 ryml::Tree langs = ryml::parse_in_arena(R"(
@@ -595,6 +659,8 @@ fr: Planète (Gazeuse)
 ru: Планета (Газ)
 ja: 惑星（ガス）
 zh: 行星（气体）
+# UTF8 decoding only happens in double-quoted strings,\
+# as per the YAML standard
 decode this: "\u263A \xE2\x98\xBA"
 and this as well: "\u2705 \U0001D11E"
 )");
@@ -604,8 +670,9 @@ CHECK(langs["fr"].val() == "Planète (Gazeuse)");
 CHECK(langs["ru"].val() == "Планета (Газ)");
 CHECK(langs["ja"].val() == "惑星（ガス）");
 CHECK(langs["zh"].val() == "行星（气体）");
-// and \x \u \U codepoints are decoded (but only when
-// they appear inside double-quoted strings):
+// and \x \u \U codepoints are decoded (but only when they appear
+// inside double-quoted strings, as dictated by the YAML
+// standard):
 CHECK(langs["decode this"].val() == "☺ ☺");
 CHECK(langs["and this as well"].val() == "✅ 𝄞");
 
@@ -617,7 +684,6 @@ ryml::Location loc = parser.location(tree2["bar"][1]);
 CHECK(parser.location_contents(loc).begins_with("30"));
 CHECK(loc.line == 3u);
 CHECK(loc.col == 4u);
-// For further details in location tracking, refer to the sample function.
 ```
 
 The [quickstart.cpp sample](./samples/quickstart.cpp) (from which the
@@ -647,13 +713,14 @@ sample_emit_to_container();    ///< emit to memory, eg a string or vector-like c
 sample_emit_to_stream();       ///< emit to a stream, eg std::ostream
 sample_emit_to_file();         ///< emit to a FILE*
 sample_emit_nested_node();     ///< pick a nested node as the root when emitting
-sample_json();                 ///< JSON parsing and emitting: notes and constraints
+sample_json();                 ///< JSON parsing and emitting
 sample_anchors_and_aliases();  ///< deal with YAML anchors and aliases
 sample_tags();                 ///< deal with YAML type tags
 sample_docs();                 ///< deal with YAML docs
 sample_error_handler();        ///< set a custom error handler
 sample_global_allocator();     ///< set a global allocator for ryml
 sample_per_tree_allocator();   ///< set per-tree allocators
+sample_static_trees();         ///< how to use static trees in ryml
 sample_location_tracking();    ///< track node locations in the parsed source tree
 ```
 
diff --git a/bm/CMakeLists.txt b/bm/CMakeLists.txt
index e9760c04a..fd4a7d9d8 100644
--- a/bm/CMakeLists.txt
+++ b/bm/CMakeLists.txt
@@ -33,6 +33,8 @@ target_include_directories(miniyaml PUBLIC
     $<BUILD_INTERFACE:${miniyaml_dir}>
     $<BUILD_INTERFACE:${miniyaml_dir}/yaml>
     $<INSTALL_INTERFACE:include>)
+c4_set_folder_remote_project_targets(ext
+    miniyaml)
 
 # yaml-cpp
 c4_import_remote_proj(yaml-cpp ${_ed}/yaml-cpp REMOTE
diff --git a/changelog/current.md b/changelog/current.md
index 6bf8d590e..f0a08b027 100644
--- a/changelog/current.md
+++ b/changelog/current.md
@@ -1,3 +1,33 @@
+
+### Modifications
+
+- **BREAKING CHANGE**: make the node API const-correct ([PR#267](https://github.com/biojppm/rapidyaml/pull/267)): added `ConstNodeRef` to hold a constant reference to a node. As the name implies, a `ConstNodeRef` object cannot be used in any tree-mutating operation. It is also smaller than the existing `NodeRef` (and faster because it does not need to check its own validity on every access). As a result of this change, there are now some constraints when obtaining a ref from a tree, and existing code is likely to break in this type of situation:
+  ```c++
+  const Tree const_tree = ...;
+  NodeRef nr = const_tree.rootref(); // ERROR (was ok): cannot obtain a mutating NodeRef from a const Tree
+  ConstNodeRef cnr = const_tree.rootref(); // ok
+  
+  Tree tree = ...;
+  NodeRef nr = tree.rootref(); // ok
+  ConstNodeRef cnr = tree.rootref(); // ok (implicit conversion from NodeRef to ConstNodeRef)
+  // to obtain a ConstNodeRef from a mutable Tree
+  // while avoiding implicit conversion, use the `c`
+  // prefix:
+  ConstNodeRef cnr = tree.crootref();
+  // likewise for tree.ref() and tree.cref().
+  
+  nr = cnr; // ERROR: cannot obtain NodeRef from ConstNodeRef
+  cnr = nr; // ok
+  ```
+  Naturally, use of `ConstNodeRef` needs to be propagated through client code. One such place is when deserializing types:
+  ```c++
+  // needs to be changed from:
+  template<class T> bool read(ryml::NodeRef const& n, T *var);
+  // ... to:
+  template<class T> bool read(ryml::ConstNodeRef const& n, T *var);
+  ```
+
+
 ### Fixes
 
 - Fix [#233](https://github.com/biojppm/rapidyaml/issues/233) - accept leading colon in the first key of a flow map (`UNK` node) [PR#234](https://github.com/biojppm/rapidyaml/pull/234):
diff --git a/samples/quickstart.cpp b/samples/quickstart.cpp
index 380ccfa30..511f4af7f 100644
--- a/samples/quickstart.cpp
+++ b/samples/quickstart.cpp
@@ -30,15 +30,15 @@
 #elif defined(RYML_SINGLE_HEADER_LIB) // using the single header from a library
     #include <ryml_all.hpp>
 #else
-    // <ryml_std.hpp> is only needed if interop with std types is
+    #include <ryml.hpp>
+    // <ryml_std.hpp> is needed if interop with std containers is
     // desired; ryml itself does not use any STL container.
     // For this sample, we will be using std interop, so...
-    #include <ryml_std.hpp> // optional header. BUT when used, needs to be included BEFORE ryml.hpp
-    #include <ryml.hpp>
-    #include <c4/format.hpp> // needed only needed for the examples below
+    #include <ryml_std.hpp> // optional header, provided for std:: interop
+    #include <c4/format.hpp> // needed for the examples below
 #endif
 
-// these are only needed for the examples below
+// these are needed for the examples below
 #include <iostream>
 #include <sstream>
 #include <vector>
@@ -159,7 +159,7 @@ void sample_quick_overview()
     //   - reuse an existing tree (advised)
     //   - reuse an existing parser (advised)
     char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
-    ryml::Tree tree = ryml::parse_in_place(ryml::substr(yml_buf));
+    ryml::Tree tree = ryml::parse_in_place(yml_buf);
 
     // Note: it will always be significantly faster to use mutable
     // buffers and reuse tree+parser.
@@ -185,14 +185,14 @@ void sample_quick_overview()
 
     // The node API is a lightweight abstraction sitting on top of the
     // index API, but offering a much more convenient interaction:
-    ryml::NodeRef root = tree.rootref();
-    ryml::NodeRef bar = tree["bar"];
+    ryml::ConstNodeRef root = tree.rootref();
+    ryml::ConstNodeRef bar = tree["bar"];
     CHECK(root.is_map());
     CHECK(bar.is_seq());
-    // NodeRef is a lightweight handle to the tree and associated id:
-    CHECK(root.tree() == &tree); // NodeRef points at its tree, WITHOUT refcount
-    CHECK(root.id() == root_id); // NodeRef's id is the index of the node
-    CHECK(bar.id() == bar_id);   // NodeRef's id is the index of the node
+    // A node ref is a lightweight handle to the tree and associated id:
+    CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount
+    CHECK(root.id() == root_id); // a node ref's id is the index of the node
+    CHECK(bar.id() == bar_id);   // a node ref's id is the index of the node
 
     // The node API translates very cleanly to the index API, so most
     // of the code examples below are using the node API.
@@ -206,14 +206,14 @@ void sample_quick_overview()
     //------------------------------------------------------------------
     // To read the parsed tree
 
-    // Node::operator[] does a lookup, is O(num_children[node]).
-    // maps use string keys, seqs use integral keys.
+    // ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
     CHECK(tree["foo"].is_keyval());
     CHECK(tree["foo"].key() == "foo");
     CHECK(tree["foo"].val() == "1");
     CHECK(tree["bar"].is_seq());
     CHECK(tree["bar"].has_key());
     CHECK(tree["bar"].key() == "bar");
+    // maps use string keys, seqs use integral keys:
     CHECK(tree["bar"][0].val() == "2");
     CHECK(tree["bar"][1].val() == "3");
     CHECK(tree["john"].val() == "doe");
@@ -223,12 +223,11 @@ void sample_quick_overview()
     CHECK(tree[0].id() == tree["foo"].id());
     CHECK(tree[1].id() == tree["bar"].id());
     CHECK(tree[2].id() == tree["john"].id());
-    // Tree::operator[](int) searches a root child by its position.
+    // Tree::operator[](int) searches a ***root*** child by its position.
     CHECK(tree[0].id() == tree["foo"].id());  // 0: first child of root
     CHECK(tree[1].id() == tree["bar"].id());  // 1: first child of root
     CHECK(tree[2].id() == tree["john"].id()); // 2: first child of root
-    // NodeRef::operator[](int) searches a node child by its position
-    // on __the node__'s children list:
+    // NodeRef::operator[](int) searches a ***node*** child by its position:
     CHECK(bar[0].val() == "2"); // 0 means first child of bar
     CHECK(bar[1].val() == "3"); // 1 means second child of bar
     // NodeRef::operator[](string):
@@ -246,31 +245,36 @@ void sample_quick_overview()
     CHECK(root["bar"].id() == root[1].id());
     CHECK(root["john"].id() == root[2].id());
 
-    // Please note that since a ryml tree uses indexed linked lists for storing
-    // children, the complexity of `Tree::operator[csubstr]` and
-    // `Tree::operator[size_t]` is linear on the number of root children. If you use
-    // it with a large tree where the root has many children, you may get a
-    // performance hit. To avoid this hit, you can create your own accelerator
-    // structure. For example, before doing a lookup, do a single traverse at the
-    // root level to fill an `map<csubstr,size_t>` mapping key names to node
-    // indices; with a node index, a lookup (via `Tree::get()`) is O(1), so this way
-    // you can get O(log n) lookup from a key. (But please do not use `std::map`
-    // if you care about performance; use something else like a flat map or
+    // IMPORTANT. The ryml tree uses indexed linked lists for storing
+    // children, so the complexity of `Tree::operator[csubstr]` and
+    // `Tree::operator[size_t]` is linear on the number of root
+    // children. If you use `Tree::operator[]` with a large tree where
+    // the root has many children, you will see a performance hit.
+    //
+    // To avoid this hit, you can create your own accelerator
+    // structure. For example, before doing a lookup, do a single
+    // traverse at the root level to fill an `map<csubstr,size_t>`
+    // mapping key names to node indices; with a node index, a lookup
+    // (via `Tree::get()`) is O(1), so this way you can get O(log n)
+    // lookup from a key. (But please do not use `std::map` if you
+    // care about performance; use something else like a flat map or
     // sorted vector).
     //
-    // As for `NodeRef`, the difference from `NodeRef::operator[]`
-    // to `Tree::operator[]` is that the latter refers to the root node, whereas
-    // the former can be invoked on any node. But the lookup process is the same for
-    // both and their algorithmic complexity is the same: they are both linear in
-    // the number of direct children; but depending on the data, that number may
-    // be very different from one to another.
+    // As for node refs, the difference from `NodeRef::operator[]` and
+    // `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
+    // latter refers to the root node, whereas the former are invoked
+    // on their target node. But the lookup process works the same for
+    // both and their algorithmic complexity is the same: they are
+    // both linear in the number of direct children. But of course,
+    // depending on the data, that number may be very different from
+    // one to another.
 
     //------------------------------------------------------------------
     // Hierarchy:
 
     {
-        ryml::NodeRef foo = root.first_child();
-        ryml::NodeRef john = root.last_child();
+        ryml::ConstNodeRef foo = root.first_child();
+        ryml::ConstNodeRef john = root.last_child();
         CHECK(tree.size() == 6); // O(1) number of nodes in the tree
         CHECK(root.num_children() == 3); // O(num_children[root])
         CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
@@ -295,13 +299,13 @@ void sample_quick_overview()
         // iterate children using the high-level node API:
         {
             size_t count = 0;
-            for(ryml::NodeRef const& child : root.children())
+            for(ryml::ConstNodeRef const& child : root.children())
                 CHECK(child.key() == expected_keys[count++]);
         }
         // iterate siblings using the high-level node API:
         {
             size_t count = 0;
-            for(ryml::NodeRef const& child : root["foo"].siblings())
+            for(ryml::ConstNodeRef const& child : root["foo"].siblings())
                 CHECK(child.key() == expected_keys[count++]);
         }
         // iterate children using the lower-level tree index API:
@@ -351,13 +355,19 @@ void sample_quick_overview()
     //------------------------------------------------------------------
     // Modifying existing nodes: operator<< vs operator=
 
+    // As implied by its name, ConstNodeRef is a reference to a const
+    // node. It can be used to read from the node, but not write to it
+    // or modify the hierarchy of the node. If any modification is
+    // desired then a NodeRef must be used instead:
+    ryml::NodeRef wroot = tree.rootref();
+
     // operator= assigns an existing string to the receiving node.
     // This pointer will be in effect until the tree goes out of scope
     // so beware to only assign from strings outliving the tree.
-    root["foo"] = "says you";
-    root["bar"][0] = "-2";
-    root["bar"][1] = "-3";
-    root["john"] = "ron";
+    wroot["foo"] = "says you";
+    wroot["bar"][0] = "-2";
+    wroot["bar"][1] = "-3";
+    wroot["john"] = "ron";
     // Now the tree is _pointing_ at the memory of the strings above.
     // That is OK because those are static strings and will outlive
     // the tree.
@@ -376,10 +386,10 @@ void sample_quick_overview()
     // assigns the serialized string to the receiving node. This avoids
     // constraints with the lifetime, since the arena lives with the tree.
     CHECK(tree.arena().empty());
-    root["foo"] << "says who";  // requires to_chars(). see serialization samples below.
-    root["bar"][0] << 20;
-    root["bar"][1] << 30;
-    root["john"] << "deere";
+    wroot["foo"] << "says who";  // requires to_chars(). see serialization samples below.
+    wroot["bar"][0] << 20;
+    wroot["bar"][1] << 30;
+    wroot["john"] << "deere";
     CHECK(root["foo"].val() == "says who");
     CHECK(root["bar"][0].val() == "20");
     CHECK(root["bar"][1].val() == "30");
@@ -389,7 +399,7 @@ void sample_quick_overview()
     {
         std::string ok("in_scope");
         // root["john"] = ryml::to_csubstr(ok); // don't, will dangle
-        root["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
+        wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
     }
     CHECK(root["john"] == "in_scope"); // OK!
     CHECK(tree.arena() == "says who2030deerein_scope"); // the result of serializations to the tree arena
@@ -400,40 +410,41 @@ void sample_quick_overview()
 
     // adding a keyval node to a map:
     CHECK(root.num_children() == 3);
-    root["newkeyval"] = "shiny and new"; // using these strings
-    root.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization
+    wroot["newkeyval"] = "shiny and new"; // using these strings
+    wroot.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization
     CHECK(root.num_children() == 5);
     CHECK(root["newkeyval"].key() == "newkeyval");
     CHECK(root["newkeyval"].val() == "shiny and new");
     CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
     CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
-    CHECK( ! root["newkeyval"].key().is_sub(tree.arena())); // it's using directly the static string above
-    CHECK( ! root["newkeyval"].val().is_sub(tree.arena())); // it's using directly the static string above
-    CHECK(   root["newkeyval (serialized)"].key().is_sub(tree.arena())); // it's using a serialization of the string above
-    CHECK(   root["newkeyval (serialized)"].val().is_sub(tree.arena())); // it's using a serialization of the string above
+    CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
+    CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
+    CHECK(   tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
+    CHECK(   tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
     // adding a val node to a seq:
     CHECK(root["bar"].num_children() == 2);
-    root["bar"][2] = "oh so nice";
-    root["bar"][3] << "oh so nice (serialized)";
+    wroot["bar"][2] = "oh so nice";
+    wroot["bar"][3] << "oh so nice (serialized)";
     CHECK(root["bar"].num_children() == 4);
     CHECK(root["bar"][2].val() == "oh so nice");
     CHECK(root["bar"][3].val() == "oh so nice (serialized)");
     // adding a seq node:
     CHECK(root.num_children() == 5);
-    root["newseq"] |= ryml::SEQ;
-    root.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
+    wroot["newseq"] |= ryml::SEQ;
+    wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
     CHECK(root.num_children() == 7);
     CHECK(root["newseq"].num_children() == 0);
     CHECK(root["newseq (serialized)"].num_children() == 0);
     // adding a map node:
     CHECK(root.num_children() == 7);
-    root["newmap"] |= ryml::MAP;
-    root.append_child() << ryml::key("newmap (serialized)") |= ryml::SEQ;
+    wroot["newmap"] |= ryml::MAP;
+    wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::SEQ;
     CHECK(root.num_children() == 9);
     CHECK(root["newmap"].num_children() == 0);
     CHECK(root["newmap (serialized)"].num_children() == 0);
-    // operator[] does not mutate the tree until the returned node is
-    // written to.
+    //
+    // When the tree is mutable, operator[] does not mutate the tree
+    // until the returned node is written to.
     //
     // Until such time, the NodeRef object keeps in itself the required
     // information to write to the proper place in the tree. This is
@@ -441,25 +452,44 @@ void sample_quick_overview()
     //
     // This means that passing a key/index which does not exist will
     // not mutate the tree, but will instead store (in the node) the
-    // proper place of the tree to do so if and when it is required.
+    // proper place of the tree to be able to do so, if and when it is
+    // required.
     //
     // This is a significant difference from eg, the behavior of
     // std::map, which mutates the map immediately within the call to
     // operator[].
-    CHECK(!root.has_child("I am nobody"));
-    ryml::NodeRef nobody = root["I am nobody"];
-    CHECK(nobody.valid());   // points at the tree, and a specific place in the tree
-    CHECK(nobody.is_seed()); // ... but nothing is there yet.
-    CHECK(!root.has_child("I am nobody")); // same as above
-    ryml::NodeRef somebody = root["I am somebody"];
-    CHECK(!root.has_child("I am somebody")); // same as above
-    CHECK(somebody.valid());
-    CHECK(somebody.is_seed()); // same as above
-    somebody = "indeed";  // this will commit to the tree, mutating at the proper place
-    CHECK(somebody.valid());
-    CHECK(!somebody.is_seed()); // now the tree has this node, and it is no longer a seed
-    CHECK(root.has_child("I am somebody"));
-    CHECK(root["I am somebody"].val() == "indeed");
+    //
+    // All of the points above apply only if the tree is mutable. If
+    // the tree is const, then a NodeRef cannot be obtained from it;
+    // only a ConstNodeRef, which can never be used to mutate the
+    // tree.
+    CHECK(!root.has_child("I am not nothing"));
+    ryml::NodeRef nothing = wroot["I am nothing"];
+    CHECK(nothing.valid());   // points at the tree, and a specific place in the tree
+    CHECK(nothing.is_seed()); // ... but nothing is there yet.
+    CHECK(!root.has_child("I am nothing")); // same as above
+    ryml::NodeRef something = wroot["I am something"];
+    ryml::ConstNodeRef constsomething = wroot["I am something"];
+    CHECK(!root.has_child("I am something")); // same as above
+    CHECK(something.valid());
+    CHECK(something.is_seed()); // same as above
+    CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef
+                                    // cannot be used to mutate a
+                                    // tree, it is only valid() if it
+                                    // is pointing at an existing
+                                    // node.
+    something = "indeed";  // this will commit to the tree, mutating at the proper place
+    CHECK(root.has_child("I am something"));
+    CHECK(root["I am something"].val() == "indeed");
+    CHECK(something.valid());
+    CHECK(!something.is_seed()); // now the tree has this node, so the
+                                 // ref is no longer a seed
+    // now the constref is also valid (but it needs to be reassigned):
+    ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
+    CHECK(constsomethingnew.valid());
+    // note that the old constref is now stale, because it only keeps
+    // the state at creation:
+    CHECK(!constsomething.valid());
 
 
     //------------------------------------------------------------------
@@ -490,7 +520,7 @@ newseq: []
 newseq (serialized): []
 newmap: {}
 newmap (serialized): []
-I am somebody: indeed
+I am something: indeed
 )";
     CHECK(buf_result == expected_result);
     CHECK(str_result == expected_result);
@@ -498,6 +528,38 @@ I am somebody: indeed
     // There are many possibilities to emit to buffer;
     // please look at the emit sample functions below.
 
+    //------------------------------------------------------------------
+    // ConstNodeRef vs NodeRef
+
+    ryml::NodeRef noderef = tree["bar"][0];
+    ryml::ConstNodeRef constnoderef = tree["bar"][0];
+
+    // ConstNodeRef cannot be used to mutate the tree, but a NodeRef can:
+    //constnoderef = "21";  // compile error
+    //constnoderef << "22"; // compile error
+    noderef = "21";         // ok, can assign because it's not const
+    CHECK(tree["bar"][0].val() == "21");
+    noderef << "22";        // ok, can serialize and assign because it's not const
+    CHECK(tree["bar"][0].val() == "22");
+
+    // it is not possible to obtain a NodeRef from a ConstNodeRef:
+    // noderef = constnoderef; // compile error
+
+    // it is always possible to obtain a ConstNodeRef from a NodeRef:
+    constnoderef = noderef;    // ok can assign const <- nonconst
+
+    // If a tree is const, then only ConstNodeRef's can be
+    // obtained from that tree:
+    ryml::Tree const& consttree = tree;
+    //noderef = consttree["bar"][0];    // compile error
+    noderef = tree["bar"][0];           // ok
+    constnoderef = consttree["bar"][0]; // ok
+
+    // ConstNodeRef and NodeRef can be compared for equality.
+    // Equality means they point at the same node.
+    CHECK(constnoderef == noderef);
+    CHECK(!(constnoderef != noderef));
+
     //------------------------------------------------------------------
     // Dealing with UTF8
     ryml::Tree langs = ryml::parse_in_arena(R"(
@@ -506,6 +568,8 @@ fr: Planète (Gazeuse)
 ru: Планета (Газ)
 ja: 惑星（ガス）
 zh: 行星（气体）
+# UTF8 decoding only happens in double-quoted strings,\
+# as per the YAML standard
 decode this: "\u263A \xE2\x98\xBA"
 and this as well: "\u2705 \U0001D11E"
 )");
@@ -515,8 +579,9 @@ and this as well: "\u2705 \U0001D11E"
     CHECK(langs["ru"].val() == "Планета (Газ)");
     CHECK(langs["ja"].val() == "惑星（ガス）");
     CHECK(langs["zh"].val() == "行星（气体）");
-    // and \x \u \U codepoints are decoded (but only when
-    // they appear inside double-quoted strings):
+    // and \x \u \U codepoints are decoded (but only when they appear
+    // inside double-quoted strings, as dictated by the YAML
+    // standard):
     CHECK(langs["decode this"].val() == "☺ ☺");
     CHECK(langs["and this as well"].val() == "✅ 𝄞");
 
@@ -1666,7 +1731,7 @@ void sample_parse_reuse_tree_and_parser()
 /** shows how to programatically iterate through trees */
 void sample_iterate_trees()
 {
-    ryml::Tree tree = ryml::parse_in_arena(R"(doe: "a deer, a female deer"
+    const ryml::Tree tree = ryml::parse_in_arena(R"(doe: "a deer, a female deer"
 ray: "a drop of golden sun"
 pi: 3.14159
 xmas: true
@@ -1686,12 +1751,12 @@ xmas-fifth-day:
   turtle-doves: two
 cars: GTO
 )");
-    ryml::NodeRef root = tree.rootref();
+    ryml::ConstNodeRef root = tree.rootref();
 
     // iterate children
     {
         std::vector<ryml::csubstr> keys, vals; // to store all the root-level keys, vals
-        for(ryml::NodeRef n : root.children())
+        for(ryml::ConstNodeRef n : root.children())
         {
             keys.emplace_back(n.key());
             vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{});
@@ -1717,7 +1782,7 @@ cars: GTO
     {
         size_t count = 0;
         ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"};
-        for(ryml::NodeRef n : root["calling-birds"][2].siblings())
+        for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings())
             CHECK(n.val() == calling_birds[count++]);
     }
 }
@@ -2720,7 +2785,7 @@ void write(ryml::NodeRef *n, my_seq_type<T> const& seq)
         n->append_child() << v;
 }
 template<class T>
-bool read(ryml::NodeRef const& n, my_seq_type<T> *seq)
+bool read(ryml::ConstNodeRef const& n, my_seq_type<T> *seq)
 {
     seq->seq_member.resize(n.num_children()); // num_children() is O(N)
     size_t pos = 0;
@@ -2740,7 +2805,7 @@ void write(ryml::NodeRef *n, my_map_type<K, V> const& map)
         n->append_child() << ryml::key(v.first) << v.second;
 }
 template<class K, class V>
-bool read(ryml::NodeRef const& n, my_map_type<K, V> *map)
+bool read(ryml::ConstNodeRef const& n, my_map_type<K, V> *map)
 {
     K k{};
     V v{};
@@ -2772,7 +2837,7 @@ void write(ryml::NodeRef *n, my_type const& val)
     n->append_child() << ryml::key("seq") << val.seq;
     n->append_child() << ryml::key("map") << val.map;
 }
-bool read(ryml::NodeRef const& n, my_type *val)
+bool read(ryml::ConstNodeRef const& n, my_type *val)
 {
     n["v2"] >> val->v2;
     n["v3"] >> val->v3;
@@ -2796,7 +2861,7 @@ void sample_user_container_types()
 
     ryml::Tree t;
     t.rootref() << mt_in;  // read from this
-    t.rootref() >> mt_out; // assign here
+    t.crootref() >> mt_out; // assign here
     CHECK(mt_out.v2.x == mt_in.v2.x);
     CHECK(mt_out.v2.y == mt_in.v2.y);
     CHECK(mt_out.v3.x == mt_in.v3.x);
@@ -3347,7 +3412,7 @@ a: b
 )";
     ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved));
     CHECK(ryml::emitrs<std::string>(tree) == resolved);
-    const ryml::NodeRef stream = tree.rootref();
+    const ryml::ConstNodeRef stream = tree.rootref();
     CHECK(stream.is_stream());
     CHECK(stream.num_children() == 13);
     for(auto node : stream.children())
@@ -3378,12 +3443,12 @@ d: 3
     // iteration through docs
     {
         // using the node API
-        const ryml::NodeRef stream = tree.rootref();
+        const ryml::ConstNodeRef stream = tree.rootref();
         CHECK(stream.is_root());
         CHECK(stream.is_stream());
         CHECK(!stream.is_doc());
         CHECK(stream.num_children() == 3);
-        for(const ryml::NodeRef doc : stream.children())
+        for(const ryml::ConstNodeRef doc : stream.children())
             CHECK(doc.is_doc());
         CHECK(tree.docref(0).id() == stream.child(0).id());
         CHECK(tree.docref(1).id() == stream.child(1).id());
@@ -3457,9 +3522,9 @@ d: 3
         // using the node API
         {
             size_t count = 0;
-            const ryml::NodeRef stream = tree.rootref();
+            const ryml::ConstNodeRef stream = tree.rootref();
             CHECK(stream.num_children() == C4_COUNTOF(expected_json));
-            for(const ryml::NodeRef doc : stream.children())
+            for(ryml::ConstNodeRef doc : stream.children())
                 CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
         }
         // equivalent: using the index API
diff --git a/src/c4/yml/detail/print.hpp b/src/c4/yml/detail/print.hpp
index df9ad25c5..f88dc251d 100644
--- a/src/c4/yml/detail/print.hpp
+++ b/src/c4/yml/detail/print.hpp
@@ -95,7 +95,7 @@ inline size_t print_node(Tree const& p, size_t node, int level, size_t count, bo
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
-inline void print_node(NodeRef const& p, int level=0)
+inline void print_node(ConstNodeRef const& p, int level=0)
 {
     print_node(*p.tree(), p.id(), level, 0, true);
 }
diff --git a/src/c4/yml/emit.def.hpp b/src/c4/yml/emit.def.hpp
index 2898eaca1..ab159ee82 100644
--- a/src/c4/yml/emit.def.hpp
+++ b/src/c4/yml/emit.def.hpp
@@ -36,7 +36,7 @@ substr Emitter<Writer>::emit(EmitType_e type, Tree const& t, bool error_on_exces
 }
 
 template<class Writer>
-substr Emitter<Writer>::emit(EmitType_e type, NodeRef const& n, bool error_on_excess)
+substr Emitter<Writer>::emit(EmitType_e type, ConstNodeRef const& n, bool error_on_excess)
 {
     _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
     return emit(type, *n.tree(), n.id(), error_on_excess);
diff --git a/src/c4/yml/emit.hpp b/src/c4/yml/emit.hpp
index 87325b46f..166837633 100644
--- a/src/c4/yml/emit.hpp
+++ b/src/c4/yml/emit.hpp
@@ -36,7 +36,7 @@ struct as_json
     size_t node;
     as_json(Tree const& t) : tree(&t), node(t.empty() ? NONE : t.root_id()) {}
     as_json(Tree const& t, size_t id) : tree(&t), node(id) {}
-    as_json(NodeRef const& n) : tree(n.tree()), node(n.id()) {}
+    as_json(ConstNodeRef const& n) : tree(n.tree()), node(n.id()) {}
 };
 
 
@@ -64,7 +64,7 @@ class Emitter : public Writer
     /** emit starting at the root node */
     substr emit(EmitType_e type, Tree const& t, bool error_on_excess=true);
     /** emit the given node */
-    substr emit(EmitType_e type, NodeRef const& n, bool error_on_excess=true);
+    substr emit(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true);
 
 private:
 
@@ -156,7 +156,7 @@ inline size_t emit_json(Tree const& t, FILE *f=nullptr)
 /** emit YAML to the given file. A null file defaults to stdout.
  * Return the number of bytes written.
  * @overload */
-inline size_t emit(NodeRef const& r, FILE *f=nullptr)
+inline size_t emit(ConstNodeRef const& r, FILE *f=nullptr)
 {
     EmitterFile em(f);
     return em.emit(EMIT_YAML, r, /*error_on_excess*/true).len;
@@ -165,7 +165,7 @@ inline size_t emit(NodeRef const& r, FILE *f=nullptr)
 /** emit JSON to the given file. A null file defaults to stdout.
  * Return the number of bytes written.
  * @overload */
-inline size_t emit_json(NodeRef const& r, FILE *f=nullptr)
+inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr)
 {
     EmitterFile em(f);
     return em.emit(EMIT_JSON, r, /*error_on_excess*/true).len;
@@ -186,7 +186,7 @@ inline OStream& operator<< (OStream& s, Tree const& t)
 /** emit YAML to an STL-like ostream
  * @overload */
 template<class OStream>
-inline OStream& operator<< (OStream& s, NodeRef const& n)
+inline OStream& operator<< (OStream& s, ConstNodeRef const& n)
 {
     EmitterOStream<OStream> em(s);
     em.emit(EMIT_YAML, n);
@@ -248,7 +248,7 @@ inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true)
  * @param error_on_excess Raise an error if the space in the buffer is insufficient.
  * @overload
  */
-inline substr emit(NodeRef const& r, substr buf, bool error_on_excess=true)
+inline substr emit(ConstNodeRef const& r, substr buf, bool error_on_excess=true)
 {
     EmitterBuf em(buf);
     return em.emit(EMIT_YAML, r, error_on_excess);
@@ -258,7 +258,7 @@ inline substr emit(NodeRef const& r, substr buf, bool error_on_excess=true)
  * @param error_on_excess Raise an error if the space in the buffer is insufficient.
  * @overload
  */
-inline substr emit_json(NodeRef const& r, substr buf, bool error_on_excess=true)
+inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess=true)
 {
     EmitterBuf em(buf);
     return em.emit(EMIT_JSON, r, error_on_excess);
@@ -370,7 +370,7 @@ CharOwningContainer emitrs_json(Tree const& t)
 /** emit+resize: YAML to the given std::string/std::vector-like container,
  * resizing it as needed to fit the emitted YAML. */
 template<class CharOwningContainer>
-substr emitrs(NodeRef const& n, CharOwningContainer * cont)
+substr emitrs(ConstNodeRef const& n, CharOwningContainer * cont)
 {
     _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
     return emitrs(*n.tree(), n.id(), cont);
@@ -379,7 +379,7 @@ substr emitrs(NodeRef const& n, CharOwningContainer * cont)
 /** emit+resize: JSON to the given std::string/std::vector-like container,
  * resizing it as needed to fit the emitted JSON. */
 template<class CharOwningContainer>
-substr emitrs_json(NodeRef const& n, CharOwningContainer * cont)
+substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont)
 {
     _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
     return emitrs_json(*n.tree(), n.id(), cont);
@@ -389,7 +389,7 @@ substr emitrs_json(NodeRef const& n, CharOwningContainer * cont)
 /** emit+resize: YAML to the given std::string/std::vector-like container,
  * resizing it as needed to fit the emitted YAML. */
 template<class CharOwningContainer>
-CharOwningContainer emitrs(NodeRef const& n)
+CharOwningContainer emitrs(ConstNodeRef const& n)
 {
     _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
     CharOwningContainer c;
@@ -400,7 +400,7 @@ CharOwningContainer emitrs(NodeRef const& n)
 /** emit+resize: JSON to the given std::string/std::vector-like container,
  * resizing it as needed to fit the emitted JSON. */
 template<class CharOwningContainer>
-CharOwningContainer emitrs_json(NodeRef const& n)
+CharOwningContainer emitrs_json(ConstNodeRef const& n)
 {
     _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
     CharOwningContainer c;
diff --git a/src/c4/yml/node.cpp b/src/c4/yml/node.cpp
index 75fd0f03f..50c7a0b60 100644
--- a/src/c4/yml/node.cpp
+++ b/src/c4/yml/node.cpp
@@ -3,6 +3,13 @@
 namespace c4 {
 namespace yml {
 
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
 size_t NodeRef::set_key_serialized(c4::fmt::const_base64_wrapper w)
 {
     _apply_seed();
@@ -19,21 +26,5 @@ size_t NodeRef::set_val_serialized(c4::fmt::const_base64_wrapper w)
     return encoded.len;
 }
 
-size_t NodeRef::deserialize_key(c4::fmt::base64_wrapper w) const
-{
-    RYML_ASSERT( ! is_seed());
-    RYML_ASSERT(valid());
-    RYML_ASSERT(get() != nullptr);
-    return from_chars(key(), &w);
-}
-
-size_t NodeRef::deserialize_val(c4::fmt::base64_wrapper w) const
-{
-    RYML_ASSERT( ! is_seed());
-    RYML_ASSERT(valid());
-    RYML_ASSERT(get() != nullptr);
-    return from_chars(val(), &w);
-}
-
 } // namespace yml
 } // namespace c4
diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp
index 417d5e12a..24fea64d1 100644
--- a/src/c4/yml/node.hpp
+++ b/src/c4/yml/node.hpp
@@ -42,18 +42,506 @@ typename std::enable_if<   std::is_floating_point<T>::value, bool>::type
 read(NodeRef const& n, T *v);
 
 
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+// forward decls
+class NodeRef;
+class ConstNodeRef;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+namespace detail {
+
+template<class NodeRefType>
+struct child_iterator
+{
+    using value_type = NodeRefType;
+    using tree_type = typename NodeRefType::tree_type;
+
+    tree_type * C4_RESTRICT m_tree;
+    size_t m_child_id;
+
+    child_iterator(tree_type * t, size_t id) : m_tree(t), m_child_id(id) {}
+
+    child_iterator& operator++ () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->next_sibling(m_child_id); return *this; }
+    child_iterator& operator-- () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->prev_sibling(m_child_id); return *this; }
+
+    NodeRefType operator*  () const { return NodeRefType(m_tree, m_child_id); }
+    NodeRefType operator-> () const { return NodeRefType(m_tree, m_child_id); }
+
+    bool operator!= (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id != that.m_child_id; }
+    bool operator== (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id == that.m_child_id; }
+};
+
+template<class NodeRefType>
+struct children_view_
+{
+    using n_iterator = child_iterator<NodeRefType>;
+
+    n_iterator b, e;
+
+    inline children_view_(n_iterator const& C4_RESTRICT b_,
+                          n_iterator const& C4_RESTRICT e_) : b(b_), e(e_) {}
+
+    inline n_iterator begin() const { return b; }
+    inline n_iterator end  () const { return e; }
+};
+
+template<class NodeRefType, class Visitor>
+bool _visit(NodeRefType &node, Visitor fn, size_t indentation_level, bool skip_root=false)
+{
+    size_t increment = 0;
+    if( ! (node.is_root() && skip_root))
+    {
+        if(fn(node, indentation_level))
+            return true;
+        ++increment;
+    }
+    if(node.has_children())
+    {
+        for(auto ch : node.children())
+        {
+            if(_visit(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+template<class NodeRefType, class Visitor>
+bool _visit_stacked(NodeRefType &node, Visitor fn, size_t indentation_level, bool skip_root=false)
+{
+    size_t increment = 0;
+    if( ! (node.is_root() && skip_root))
+    {
+        if(fn(node, indentation_level))
+        {
+            return true;
+        }
+        ++increment;
+    }
+    if(node.has_children())
+    {
+        fn.push(node, indentation_level);
+        for(auto ch : node.children())
+        {
+            if(_visit_stacked(ch, fn, indentation_level + increment, false)) // no need to forward skip_root as it won't be root
+            {
+                fn.pop(node, indentation_level);
+                return true;
+            }
+        }
+        fn.pop(node, indentation_level);
+    }
+    return false;
+}
+
+
+//-----------------------------------------------------------------------------
+
+/** a CRTP base for read-only node methods */
+template<class Impl, class ConstImpl>
+struct RoNodeMethods
+{
+    C4_SUPPRESS_WARNING_CLANG_WITH_PUSH("-Wcast-align")
+    // helper CRTP macros, undefined at the end
+    #define tree_ ((ConstImpl const* C4_RESTRICT)this)->m_tree
+    #define id_ ((ConstImpl const* C4_RESTRICT)this)->m_id
+    // require valid
+    #define _C4RV()                                       \
+        RYML_ASSERT(tree_ != nullptr);                    \
+        _RYML_CB_ASSERT(tree_->m_callbacks, id_ != NONE)
+
+public:
+
+    /** @name node property getters */
+    /** @{ */
+
+    /** returns the data or null when the id is NONE */
+    C4_ALWAYS_INLINE C4_PURE NodeData const* get() const noexcept { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE NodeType    type() const noexcept { _C4RV(); return tree_->type(id_); }
+    C4_ALWAYS_INLINE C4_PURE const char* type_str() const noexcept { return tree_->type_str(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE csubstr key()        const noexcept { _C4RV(); return tree_->key(id_); }
+    C4_ALWAYS_INLINE C4_PURE csubstr key_tag()    const noexcept { _C4RV(); return tree_->key_tag(id_); }
+    C4_ALWAYS_INLINE C4_PURE csubstr key_ref()    const noexcept { _C4RV(); return tree_->key_ref(id_); }
+    C4_ALWAYS_INLINE C4_PURE csubstr key_anchor() const noexcept { _C4RV(); return tree_->key_anchor(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE csubstr val()        const noexcept { _C4RV(); return tree_->val(id_); }
+    C4_ALWAYS_INLINE C4_PURE csubstr val_tag()    const noexcept { _C4RV(); return tree_->val_tag(id_); }
+    C4_ALWAYS_INLINE C4_PURE csubstr val_ref()    const noexcept { _C4RV(); return tree_->val_ref(id_); }
+    C4_ALWAYS_INLINE C4_PURE csubstr val_anchor() const noexcept { _C4RV(); return tree_->val_anchor(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE NodeScalar const& keysc() const noexcept { _C4RV(); return tree_->keysc(id_); }
+    C4_ALWAYS_INLINE C4_PURE NodeScalar const& valsc() const noexcept { _C4RV(); return tree_->valsc(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE bool key_is_null() const noexcept { _C4RV(); return tree_->key_is_null(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool val_is_null() const noexcept { _C4RV(); return tree_->val_is_null(id_); }
+
+    /** @} */
+
+public:
+
+    /** @name node property predicates */
+    /** @{ */
+
+    C4_ALWAYS_INLINE C4_PURE bool empty()            const noexcept { _C4RV(); return tree_->empty(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_stream()        const noexcept { _C4RV(); return tree_->is_stream(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_doc()           const noexcept { _C4RV(); return tree_->is_doc(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_container()     const noexcept { _C4RV(); return tree_->is_container(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_map()           const noexcept { _C4RV(); return tree_->is_map(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_seq()           const noexcept { _C4RV(); return tree_->is_seq(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_val()          const noexcept { _C4RV(); return tree_->has_val(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_key()          const noexcept { _C4RV(); return tree_->has_key(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_val()           const noexcept { _C4RV(); return tree_->is_val(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_keyval()        const noexcept { _C4RV(); return tree_->is_keyval(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_key_tag()      const noexcept { _C4RV(); return tree_->has_key_tag(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_val_tag()      const noexcept { _C4RV(); return tree_->has_val_tag(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_key_anchor()   const noexcept { _C4RV(); return tree_->has_key_anchor(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_key_anchor()    const noexcept { _C4RV(); return tree_->is_key_anchor(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_val_anchor()   const noexcept { _C4RV(); return tree_->has_val_anchor(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_val_anchor()    const noexcept { _C4RV(); return tree_->is_val_anchor(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_anchor()       const noexcept { _C4RV(); return tree_->has_anchor(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_anchor()        const noexcept { _C4RV(); return tree_->is_anchor(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_key_ref()       const noexcept { _C4RV(); return tree_->is_key_ref(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_val_ref()       const noexcept { _C4RV(); return tree_->is_val_ref(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_ref()           const noexcept { _C4RV(); return tree_->is_ref(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_anchor_or_ref() const noexcept { _C4RV(); return tree_->is_anchor_or_ref(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_key_quoted()    const noexcept { _C4RV(); return tree_->is_key_quoted(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_val_quoted()    const noexcept { _C4RV(); return tree_->is_val_quoted(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool is_quoted()        const noexcept { _C4RV(); return tree_->is_quoted(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool parent_is_seq()    const noexcept { _C4RV(); return tree_->parent_is_seq(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool parent_is_map()    const noexcept { _C4RV(); return tree_->parent_is_map(id_); }
+
+    /** @} */
+
+public:
+
+    /** @name hierarchy predicates */
+    /** @{ */
+
+    C4_ALWAYS_INLINE C4_PURE bool is_root()    const noexcept { _C4RV(); return tree_->is_root(id_); }
+    C4_ALWAYS_INLINE C4_PURE bool has_parent() const noexcept { _C4RV(); return tree_->has_parent(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE bool has_child(ConstImpl const& ch) const noexcept { _C4RV(); return tree_->has_child(id_, ch.m_id); }
+    C4_ALWAYS_INLINE C4_PURE bool has_child(csubstr name) const noexcept { _C4RV(); return tree_->has_child(id_, name); }
+    C4_ALWAYS_INLINE C4_PURE bool has_children() const noexcept { _C4RV(); return tree_->has_children(id_); }
+
+    C4_ALWAYS_INLINE C4_PURE bool has_sibling(ConstImpl const& n) const noexcept { _C4RV(); return tree_->has_sibling(id_, n.m_id); }
+    C4_ALWAYS_INLINE C4_PURE bool has_sibling(csubstr name) const noexcept { _C4RV(); return tree_->has_sibling(id_, name); }
+    /** counts with this */
+    C4_ALWAYS_INLINE C4_PURE bool has_siblings() const noexcept { _C4RV(); return tree_->has_siblings(id_); }
+    /** does not count with this */
+    C4_ALWAYS_INLINE C4_PURE bool has_other_siblings() const noexcept { _C4RV(); return tree_->has_other_siblings(id_); }
+
+    /** @} */
+
+public:
+
+    /** @name hierarchy getters */
+    /** @{ */
+
+    C4_ALWAYS_INLINE C4_PURE ConstImpl parent() const noexcept { _C4RV(); return {tree_, tree_->parent(id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl prev_sibling() const noexcept { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl next_sibling() const noexcept { _C4RV(); return {tree_, tree_->next_sibling(id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl doc(size_t num) const noexcept { _C4RV(); return {tree_, tree_->doc(num)}; }
+
+    /** O(#num_children) */
+    C4_ALWAYS_INLINE C4_PURE size_t    num_children() const noexcept { _C4RV(); return tree_->num_children(id_); }
+    C4_ALWAYS_INLINE C4_PURE size_t    child_pos(ConstImpl const& n) const noexcept { _C4RV(); return tree_->child_pos(id_, n.m_id); }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl first_child() const noexcept { _C4RV(); return {tree_, tree_->first_child(id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl last_child () const noexcept { _C4RV(); return {tree_, tree_->last_child (id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl child(size_t pos) const noexcept { _C4RV(); return {tree_, tree_->child(id_, pos)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl find_child(csubstr name) const noexcept { _C4RV(); return {tree_, tree_->find_child(id_, name)}; }
+
+    /** O(#num_siblings) */
+    C4_ALWAYS_INLINE C4_PURE size_t    num_siblings() const noexcept { _C4RV(); return tree_->num_siblings(id_); }
+    C4_ALWAYS_INLINE C4_PURE size_t    num_other_siblings() const noexcept { _C4RV(); return tree_->num_other_siblings(id_); }
+    C4_ALWAYS_INLINE C4_PURE size_t    sibling_pos(ConstImpl const& n) const noexcept { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl first_sibling() const noexcept { _C4RV(); return {tree_, tree_->first_sibling(id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl last_sibling () const noexcept { _C4RV(); return {tree_, tree_->last_sibling(id_)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl sibling(size_t pos) const noexcept { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; }
+    C4_ALWAYS_INLINE C4_PURE ConstImpl find_sibling(csubstr name) const noexcept { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; }
+
+
+    /** O(num_children) */
+    C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (csubstr k) const noexcept
+    {
+        _C4RV();
+        size_t ch = tree_->find_child(id_, k);
+        _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE);
+        return {tree_, ch};
+    }
+
+    /** O(num_children) */
+    C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (size_t pos) const noexcept
+    {
+        _C4RV();
+        size_t ch = tree_->child(id_, pos);
+        _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE);
+        return {tree_, ch};
+    }
+
+    /** @} */
+
+public:
+
+    /** deserialization */
+    /** @{ */
+
+    template<class T>
+    ConstImpl const& operator>> (T &v) const
+    {
+        _C4RV();
+        if( ! read((ConstImpl const&)*this, &v))
+            _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize value");
+        return *((ConstImpl const*)this);
+    }
+
+    /** deserialize the node's key to the given variable */
+    template<class T>
+    ConstImpl const& operator>> (Key<T> v) const
+    {
+        _C4RV();
+        if( ! from_chars(key(), &v.k))
+            _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize key");
+        return *((ConstImpl const*)this);
+    }
+
+    /** deserialize the node's key as base64 */
+    ConstImpl const& operator>> (Key<fmt::base64_wrapper> w) const
+    {
+        deserialize_key(w.wrapper);
+        return *((ConstImpl const*)this);
+    }
+
+    /** deserialize the node's val as base64 */
+    ConstImpl const& operator>> (fmt::base64_wrapper w) const
+    {
+        deserialize_val(w);
+        return *((ConstImpl const*)this);
+    }
+
+    /** decode the base64-encoded key and assign the
+     * decoded blob to the given buffer/
+     * @return the size of base64-decoded blob */
+    size_t deserialize_key(fmt::base64_wrapper v) const
+    {
+        _C4RV();
+        return from_chars(key(), &v);
+    }
+    /** decode the base64-encoded key and assign the
+     * decoded blob to the given buffer/
+     * @return the size of base64-decoded blob */
+    size_t deserialize_val(fmt::base64_wrapper v) const
+    {
+        _C4RV();
+        return from_chars(val(), &v);
+    };
+
+    template<class T>
+    bool get_if(csubstr name, T *var) const
+    {
+        auto ch = find_child(name);
+        if(!ch.valid())
+            return false;
+        ch >> *var;
+        return true;
+    }
+
+    template<class T>
+    bool get_if(csubstr name, T *var, T const& fallback) const
+    {
+        auto ch = find_child(name);
+        if(ch.valid())
+        {
+            ch >> *var;
+            return true;
+        }
+        else
+        {
+            *var = fallback;
+            return false;
+        }
+    }
+
+    /** @} */
+
+public:
+
+    /** @name iteration */
+    /** @{ */
+
+    using const_iterator = detail::child_iterator<ConstImpl>;
+    using const_children_view = detail::children_view_<ConstImpl>;
+
+    C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); }
+    C4_ALWAYS_INLINE C4_PURE const_iterator end  () const noexcept { _C4RV(); return const_iterator(tree_, NONE); }
+
+    C4_ALWAYS_INLINE C4_PURE const_children_view children() const noexcept { _C4RV(); return const_children_view(begin(), end()); }
+
+    #if defined(__clang__)
+    #   pragma clang diagnostic push
+    #   pragma clang diagnostic ignored "-Wnull-dereference"
+    #elif defined(__GNUC__)
+    #   pragma GCC diagnostic push
+    #   if __GNUC__ >= 6
+    #       pragma GCC diagnostic ignored "-Wnull-dereference"
+    #   endif
+    #endif
+
+    C4_ALWAYS_INLINE C4_PURE const_children_view siblings() const noexcept
+    {
+        _C4RV();
+        if(is_root())
+            return const_children_view(end(), end());
+        size_t p = get()->m_parent;
+        return const_children_view(
+            const_iterator(tree_, tree_->get(p)->m_first_child),
+            const_iterator(tree_, NONE));
+    }
+
+    #if defined(__clang__)
+    #   pragma clang diagnostic pop
+    #elif defined(__GNUC__)
+    #   pragma GCC diagnostic pop
+    #endif
+
+    /** visit every child node calling fn(node) */
+    template<class Visitor>
+    C4_ALWAYS_INLINE C4_PURE bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const noexcept
+    {
+        return detail::_visit(*(ConstImpl*)this, fn, indentation_level, skip_root);
+    }
+
+    /** visit every child node calling fn(node, level) */
+    template<class Visitor>
+    C4_ALWAYS_INLINE C4_PURE bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const noexcept
+    {
+        return detail::_visit_stacked(*(ConstImpl*)this, fn, indentation_level, skip_root);
+    }
+
+    /** @} */
+
+    #undef _C4RV
+    #undef tree_
+    #undef id_
+    C4_SUPPRESS_WARNING_CLANG_POP
+};
+
+} // namespace detail
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods<ConstNodeRef, ConstNodeRef>
+{
+public:
+
+    using tree_type = Tree const;
+
+public:
+
+    Tree const* C4_RESTRICT m_tree;
+    size_t m_id;
+
+    friend NodeRef;
+    friend struct detail::RoNodeMethods<ConstNodeRef, ConstNodeRef>;
+
+public:
+
+    /** @name construction */
+    /** @{ */
+
+    ConstNodeRef() : m_tree(nullptr), m_id(NONE) {}
+    ConstNodeRef(Tree const &t) : m_tree(&t), m_id(t .root_id()) {}
+    ConstNodeRef(Tree const *t) : m_tree(t ), m_id(t->root_id()) {}
+    ConstNodeRef(Tree const *t, size_t id) : m_tree(t), m_id(id) {}
+    ConstNodeRef(std::nullptr_t) : m_tree(nullptr), m_id(NONE) {}
+
+    ConstNodeRef(ConstNodeRef const&) = default;
+    ConstNodeRef(ConstNodeRef     &&) = default;
+
+    ConstNodeRef(NodeRef const&);
+    ConstNodeRef(NodeRef     &&);
+
+    /** @} */
+
+public:
+
+    /** @name assignment */
+    /** @{ */
+
+    ConstNodeRef& operator= (std::nullptr_t) { m_tree = nullptr; m_id = NONE; return *this; }
+
+    ConstNodeRef& operator= (ConstNodeRef const&) = default;
+    ConstNodeRef& operator= (ConstNodeRef     &&) = default;
+
+    ConstNodeRef& operator= (NodeRef const&);
+    ConstNodeRef& operator= (NodeRef     &&);
+
+
+    /** @} */
+
+public:
+
+    /** @name state queries */
+    /** @{ */
+
+    C4_ALWAYS_INLINE C4_PURE bool valid() const noexcept { return m_tree != nullptr && m_id != NONE; }
+
+    /** @} */
+
+public:
+
+    /** @name member getters */
+    /** @{ */
+
+    C4_ALWAYS_INLINE C4_PURE Tree const* tree() const noexcept { return m_tree; }
+    C4_ALWAYS_INLINE C4_PURE size_t id() const noexcept { return m_id; }
+
+    /** @} */
+
+public:
+
+    /** @name comparisons */
+    /** @{ */
+
+    C4_ALWAYS_INLINE C4_PURE bool operator== (ConstNodeRef const& that) const noexcept { RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; }
+    C4_ALWAYS_INLINE C4_PURE bool operator!= (ConstNodeRef const& that) const noexcept { RYML_ASSERT(that.m_tree == m_tree); return ! this->operator==(that); }
+
+    C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; }
+    C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return ! this->operator== (nullptr); }
+
+    C4_ALWAYS_INLINE C4_PURE bool operator== (csubstr val) const noexcept { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; }
+    C4_ALWAYS_INLINE C4_PURE bool operator!= (csubstr val) const noexcept { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; }
+
+    /** @} */
+
+};
+
+
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 /** a reference to a node in an existing yaml tree, offering a more
  * convenient API than the index-based API used in the tree. */
-class RYML_EXPORT NodeRef
+class RYML_EXPORT NodeRef : public detail::RoNodeMethods<NodeRef, ConstNodeRef>
 {
-private:
+public:
 
-    // require valid: a helper macro, undefined at the end
-    #define _C4RV() RYML_ASSERT(valid() && !is_seed())
+    using tree_type = Tree;
+
+private:
 
     Tree *C4_RESTRICT m_tree;
     size_t m_id;
@@ -70,145 +558,87 @@ class RYML_EXPORT NodeRef
      * the size is NONE. */
     csubstr m_seed;
 
+    friend ConstNodeRef;
+    friend struct detail::RoNodeMethods<NodeRef, ConstNodeRef>;
+
+    // require valid: a helper macro, undefined at the end
+    #define _C4RV()                                                         \
+        RYML_ASSERT(m_tree != nullptr);                                     \
+        _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE && !is_seed())
+
 public:
 
-    /** @name node construction */
+    /** @name construction */
     /** @{ */
 
     NodeRef() : m_tree(nullptr), m_id(NONE), m_seed() { _clear_seed(); }
     NodeRef(Tree &t) : m_tree(&t), m_id(t .root_id()), m_seed() { _clear_seed(); }
     NodeRef(Tree *t) : m_tree(t ), m_id(t->root_id()), m_seed() { _clear_seed(); }
     NodeRef(Tree *t, size_t id) : m_tree(t), m_id(id), m_seed() { _clear_seed(); }
-    NodeRef(Tree *t, size_t id, size_t seed_pos) : m_tree(t), m_id(id), m_seed() { m_seed.str = nullptr; m_seed.len = seed_pos; }
-    NodeRef(Tree *t, size_t id, csubstr  seed_key) : m_tree(t), m_id(id), m_seed(seed_key) {}
-    NodeRef(std::nullptr_t) : m_tree(nullptr), m_id(NONE), m_seed() {}
-
-    NodeRef(NodeRef const&) = default;
-    NodeRef(NodeRef     &&) = default;
-
-    NodeRef& operator= (NodeRef const&) = default;
-    NodeRef& operator= (NodeRef     &&) = default;
+    NodeRef(Tree *t, size_t id, size_t seed_pos) : m_tree(t), m_id(id), m_seed() { m_seed.str = nullptr; m_seed.len = seed_pos; }
+    NodeRef(Tree *t, size_t id, csubstr  seed_key) : m_tree(t), m_id(id), m_seed(seed_key) {}
+    NodeRef(std::nullptr_t) : m_tree(nullptr), m_id(NONE), m_seed() {}
 
     /** @} */
 
 public:
 
-    inline Tree      * tree()       { return m_tree; }
-    inline Tree const* tree() const { return m_tree; }
-
-    inline size_t id() const { return m_id; }
-
-    inline NodeData      * get()       { return m_tree->get(m_id); }
-    inline NodeData const* get() const { return m_tree->get(m_id); }
+    /** @name assignment */
+    /** @{ */
 
-    inline bool operator== (NodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid() && !that.is_seed()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; }
-    inline bool operator!= (NodeRef const& that) const { return ! this->operator==(that); }
+    NodeRef(NodeRef const&) = default;
+    NodeRef(NodeRef     &&) = default;
 
-    inline bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); }
-    inline bool operator!= (std::nullptr_t) const { return ! this->operator== (nullptr); }
+    NodeRef& operator= (NodeRef const&) = default;
+    NodeRef& operator= (NodeRef     &&) = default;
 
-    inline bool operator== (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; }
-    inline bool operator!= (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; }
+    NodeRef& operator= (std::nullptr_t) { m_tree = nullptr; m_id = NONE; m_seed = {}; return *this; }
 
-    //inline operator bool () const { return m_tree == nullptr || m_id == NONE || is_seed(); }
+    /** @} */
 
 public:
 
+    /** @name state queries */
+    /** @{ */
+
     inline bool valid() const { return m_tree != nullptr && m_id != NONE; }
     inline bool is_seed() const { return m_seed.str != nullptr || m_seed.len != NONE; }
 
     inline void _clear_seed() { /*do this manually or an assert is triggered*/ m_seed.str = nullptr; m_seed.len = NONE; }
 
+    /** @} */
+
 public:
 
-    /** @name node property getters */
+    /** @name comparisons */
     /** @{ */
 
-    inline NodeType     type() const { _C4RV(); return m_tree->type(m_id); }
-    inline const char*  type_str() const { _C4RV(); RYML_ASSERT(valid() && ! is_seed()); return m_tree->type_str(m_id); }
-
-    inline csubstr    key()        const { _C4RV(); return m_tree->key(m_id); }
-    inline csubstr    key_tag()    const { _C4RV(); return m_tree->key_tag(m_id); }
-    inline csubstr    key_ref()    const { _C4RV(); return m_tree->key_ref(m_id); }
-    inline csubstr    key_anchor() const { _C4RV(); return m_tree->key_anchor(m_id); }
-    inline NodeScalar keysc()      const { _C4RV(); return m_tree->keysc(m_id); }
-
-    inline csubstr    val()        const { _C4RV(); return m_tree->val(m_id); }
-    inline csubstr    val_tag()    const { _C4RV(); return m_tree->val_tag(m_id); }
-    inline csubstr    val_ref()    const { _C4RV(); return m_tree->val_ref(m_id); }
-    inline csubstr    val_anchor() const { _C4RV(); return m_tree->val_anchor(m_id); }
-    inline NodeScalar valsc()      const { _C4RV(); return m_tree->valsc(m_id); }
-
-    inline bool key_is_null() const { _C4RV(); return m_tree->key_is_null(m_id); }
-    inline bool val_is_null() const { _C4RV(); return m_tree->val_is_null(m_id); }
-
-    /** decode the base64-encoded key deserialize and assign the
-     * decoded blob to the given buffer/
-     * @return the size of base64-decoded blob */
-    size_t deserialize_key(fmt::base64_wrapper v) const;
-    /** decode the base64-encoded key deserialize and assign the
-     * decoded blob to the given buffer/
-     * @return the size of base64-decoded blob */
-    size_t deserialize_val(fmt::base64_wrapper v) const;
+    inline bool operator== (NodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid() && !that.is_seed()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; }
+    inline bool operator!= (NodeRef const& that) const { return ! this->operator==(that); }
 
-    /** @} */
+    inline bool operator== (ConstNodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; }
+    inline bool operator!= (ConstNodeRef const& that) const { return ! this->operator==(that); }
 
-public:
+    inline bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); }
+    inline bool operator!= (std::nullptr_t) const { return m_tree != nullptr && m_id != NONE && !is_seed(); }
 
-    /** @name node property predicates */
-    /** @{ */
+    inline bool operator== (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; }
+    inline bool operator!= (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; }
 
-    C4_ALWAYS_INLINE bool is_stream()        const { _C4RV(); return m_tree->is_stream(m_id); }
-    C4_ALWAYS_INLINE bool is_doc()           const { _C4RV(); return m_tree->is_doc(m_id); }
-    C4_ALWAYS_INLINE bool is_container()     const { _C4RV(); return m_tree->is_container(m_id); }
-    C4_ALWAYS_INLINE bool is_map()           const { _C4RV(); return m_tree->is_map(m_id); }
-    C4_ALWAYS_INLINE bool is_seq()           const { _C4RV(); return m_tree->is_seq(m_id); }
-    C4_ALWAYS_INLINE bool has_val()          const { _C4RV(); return m_tree->has_val(m_id); }
-    C4_ALWAYS_INLINE bool has_key()          const { _C4RV(); return m_tree->has_key(m_id); }
-    C4_ALWAYS_INLINE bool is_val()           const { _C4RV(); return m_tree->is_val(m_id); }
-    C4_ALWAYS_INLINE bool is_keyval()        const { _C4RV(); return m_tree->is_keyval(m_id); }
-    C4_ALWAYS_INLINE bool has_key_tag()      const { _C4RV(); return m_tree->has_key_tag(m_id); }
-    C4_ALWAYS_INLINE bool has_val_tag()      const { _C4RV(); return m_tree->has_val_tag(m_id); }
-    C4_ALWAYS_INLINE bool has_key_anchor()   const { _C4RV(); return m_tree->has_key_anchor(m_id); }
-    C4_ALWAYS_INLINE bool is_key_anchor()    const { _C4RV(); return m_tree->is_key_anchor(m_id); }
-    C4_ALWAYS_INLINE bool has_val_anchor()   const { _C4RV(); return m_tree->has_val_anchor(m_id); }
-    C4_ALWAYS_INLINE bool is_val_anchor()    const { _C4RV(); return m_tree->is_val_anchor(m_id); }
-    C4_ALWAYS_INLINE bool has_anchor()       const { _C4RV(); return m_tree->has_anchor(m_id); }
-    C4_ALWAYS_INLINE bool is_anchor()        const { _C4RV(); return m_tree->is_anchor(m_id); }
-    C4_ALWAYS_INLINE bool is_key_ref()       const { _C4RV(); return m_tree->is_key_ref(m_id); }
-    C4_ALWAYS_INLINE bool is_val_ref()       const { _C4RV(); return m_tree->is_val_ref(m_id); }
-    C4_ALWAYS_INLINE bool is_ref()           const { _C4RV(); return m_tree->is_ref(m_id); }
-    C4_ALWAYS_INLINE bool is_anchor_or_ref() const { _C4RV(); return m_tree->is_anchor_or_ref(m_id); }
-    C4_ALWAYS_INLINE bool is_key_quoted()    const { _C4RV(); return m_tree->is_key_quoted(m_id); }
-    C4_ALWAYS_INLINE bool is_val_quoted()    const { _C4RV(); return m_tree->is_val_quoted(m_id); }
-    C4_ALWAYS_INLINE bool is_quoted()        const { _C4RV(); return m_tree->is_quoted(m_id); }
-
-    C4_ALWAYS_INLINE bool parent_is_seq()    const { _C4RV(); return m_tree->parent_is_seq(m_id); }
-    C4_ALWAYS_INLINE bool parent_is_map()    const { _C4RV(); return m_tree->parent_is_map(m_id); }
-
-    /** true when name and value are empty, and has no children */
-    C4_ALWAYS_INLINE bool empty() const { _C4RV(); return m_tree->empty(m_id); }
+    //inline operator bool () const { return m_tree == nullptr || m_id == NONE || is_seed(); }
 
     /** @} */
 
 public:
 
-    /** @name hierarchy predicates */
+    /** @name non-const node property getters */
     /** @{ */
 
-    inline bool is_root()    const { _C4RV(); return m_tree->is_root(m_id); }
-    inline bool has_parent() const { _C4RV(); return m_tree->has_parent(m_id); }
-
-    inline bool has_child(NodeRef const& ch) const { _C4RV(); return m_tree->has_child(m_id, ch.m_id); }
-    inline bool has_child(csubstr name) const { _C4RV();  return m_tree->has_child(m_id, name); }
-    inline bool has_children() const { _C4RV(); return m_tree->has_children(m_id); }
+    inline Tree * tree() { return m_tree; }
+    inline size_t id() const { return m_id; }
 
-    inline bool has_sibling(NodeRef const& n) const { _C4RV(); return m_tree->has_sibling(m_id, n.m_id); }
-    inline bool has_sibling(csubstr name) const { _C4RV();  return m_tree->has_sibling(m_id, name); }
-    /** counts with this */
-    inline bool has_siblings() const { _C4RV(); return m_tree->has_siblings(m_id); }
-    /** does not count with this */
-    inline bool has_other_siblings() const { _C4RV(); return m_tree->has_other_siblings(m_id); }
+    /** returns the data or null when the id is NONE */
+    inline NodeData * get() { RYML_ASSERT(m_tree != nullptr); return m_tree->get(m_id); }
 
     /** @} */
 
@@ -217,42 +647,39 @@ class RYML_EXPORT NodeRef
     /** @name hierarchy getters */
     /** @{ */
 
-    NodeRef       parent()       { _C4RV(); return {m_tree, m_tree->parent(m_id)}; }
-    NodeRef const parent() const { _C4RV(); return {m_tree, m_tree->parent(m_id)}; }
-
-    NodeRef       prev_sibling()       { _C4RV(); return {m_tree, m_tree->prev_sibling(m_id)}; }
-    NodeRef const prev_sibling() const { _C4RV(); return {m_tree, m_tree->prev_sibling(m_id)}; }
-
-    NodeRef       next_sibling()       { _C4RV(); return {m_tree, m_tree->next_sibling(m_id)}; }
-    NodeRef const next_sibling() const { _C4RV(); return {m_tree, m_tree->next_sibling(m_id)}; }
+    NodeRef parent() { _C4RV(); return {m_tree, m_tree->parent(m_id)}; }
+    NodeRef prev_sibling() { _C4RV(); return {m_tree, m_tree->prev_sibling(m_id)}; }
+    NodeRef next_sibling() { _C4RV(); return {m_tree, m_tree->next_sibling(m_id)}; }
 
     /** O(#num_children) */
-    size_t  num_children() const { _C4RV(); return m_tree->num_children(m_id); }
-    size_t  child_pos(NodeRef const& n) const { _C4RV(); return m_tree->child_pos(m_id, n.m_id); }
-    NodeRef       first_child()       { _C4RV(); return {m_tree, m_tree->first_child(m_id)}; }
-    NodeRef const first_child() const { _C4RV(); return {m_tree, m_tree->first_child(m_id)}; }
-    NodeRef       last_child ()       { _C4RV(); return {m_tree, m_tree->last_child (m_id)}; }
-    NodeRef const last_child () const { _C4RV(); return {m_tree, m_tree->last_child (m_id)}; }
-    NodeRef       child(size_t pos)       { _C4RV(); return {m_tree, m_tree->child(m_id, pos)}; }
-    NodeRef const child(size_t pos) const { _C4RV(); return {m_tree, m_tree->child(m_id, pos)}; }
-    NodeRef       find_child(csubstr name)       { _C4RV(); return {m_tree, m_tree->find_child(m_id, name)}; }
-    NodeRef const find_child(csubstr name) const { _C4RV(); return {m_tree, m_tree->find_child(m_id, name)}; }
+    NodeRef first_child(){ _C4RV(); return {m_tree, m_tree->first_child(m_id)}; }
+    NodeRef last_child() { _C4RV(); return {m_tree, m_tree->last_child(m_id)}; }
+    NodeRef child(size_t pos) { _C4RV(); return {m_tree, m_tree->child(m_id, pos)}; }
+    NodeRef find_child(csubstr name) { _C4RV(); return {m_tree, m_tree->find_child(m_id, name)}; }
 
     /** O(#num_siblings) */
-    size_t  num_siblings() const { _C4RV(); return m_tree->num_siblings(m_id); }
-    size_t  num_other_siblings() const { _C4RV(); return m_tree->num_other_siblings(m_id); }
-    size_t  sibling_pos(NodeRef const& n) const { _C4RV(); return m_tree->child_pos(m_tree->parent(m_id), n.m_id); }
-    NodeRef       first_sibling()       { _C4RV(); return {m_tree, m_tree->first_sibling(m_id)}; }
-    NodeRef const first_sibling() const { _C4RV(); return {m_tree, m_tree->first_sibling(m_id)}; }
-    NodeRef       last_sibling ()       { _C4RV(); return {m_tree, m_tree->last_sibling(m_id)}; }
-    NodeRef const last_sibling () const { _C4RV(); return {m_tree, m_tree->last_sibling(m_id)}; }
-    NodeRef       sibling(size_t pos)       { _C4RV(); return {m_tree, m_tree->sibling(m_id, pos)}; }
-    NodeRef const sibling(size_t pos) const { _C4RV(); return {m_tree, m_tree->sibling(m_id, pos)}; }
-    NodeRef       find_sibling(csubstr name)       { _C4RV(); return {m_tree, m_tree->find_sibling(m_id, name)}; }
-    NodeRef const find_sibling(csubstr name) const { _C4RV(); return {m_tree, m_tree->find_sibling(m_id, name)}; }
-
-    NodeRef       doc(size_t num)       { _C4RV(); return {m_tree, m_tree->doc(num)}; }
-    NodeRef const doc(size_t num) const { _C4RV(); return {m_tree, m_tree->doc(num)}; }
+    NodeRef first_sibling() { _C4RV(); return {m_tree, m_tree->first_sibling(m_id)}; }
+    NodeRef last_sibling () { _C4RV(); return {m_tree, m_tree->last_sibling(m_id)}; }
+    NodeRef sibling(size_t pos) { _C4RV(); return {m_tree, m_tree->sibling(m_id, pos)}; }
+    NodeRef find_sibling(csubstr name) { _C4RV(); return {m_tree, m_tree->find_sibling(m_id, name)}; }
+
+    NodeRef doc(size_t num) { _C4RV(); return {m_tree, m_tree->doc(num)}; }
+
+    /** Find child by key. O(num_children). returns a seed node if no such child is found.  */
+    NodeRef operator[] (csubstr k)
+    {
+        _C4RV();
+        size_t ch = m_tree->find_child(m_id, k);
+        return ch != NONE ? NodeRef(m_tree, ch) : NodeRef(m_tree, m_id, k);
+    }
+
+    /** Find child by position. O(pos). returns a seed node if no such child is found.  */
+    NodeRef operator[] (size_t pos)
+    {
+        _C4RV();
+        size_t ch = m_tree->child(m_id, pos);
+        return ch != NONE ? NodeRef(m_tree, ch) : NodeRef(m_tree, m_id, pos);
+    }
 
     /** @} */
 
@@ -262,6 +689,7 @@ class RYML_EXPORT NodeRef
     /** @{ */
 
     void change_type(NodeType t) { _C4RV(); m_tree->change_type(m_id, t); }
+
     void set_type(NodeType t) { _C4RV(); m_tree->_set_flags(m_id, t); }
     void set_key(csubstr key) { _C4RV(); m_tree->_set_key(m_id, key); }
     void set_val(csubstr val) { _C4RV(); m_tree->_set_val(m_id, val); }
@@ -327,62 +755,6 @@ class RYML_EXPORT NodeRef
         m_tree->remove_children(m_id);
     }
 
-    /** @} */
-
-public:
-
-    /** hierarchy getters */
-    /** @{ */
-
-    /** O(num_children) */
-    NodeRef operator[] (csubstr k)
-    {
-        RYML_ASSERT( ! is_seed());
-        RYML_ASSERT(valid());
-        size_t ch = m_tree->find_child(m_id, k);
-        NodeRef r = ch != NONE ? NodeRef(m_tree, ch) : NodeRef(m_tree, m_id, k);
-        return r;
-    }
-
-    /** O(num_children) */
-    NodeRef const operator[] (csubstr k) const
-    {
-        RYML_ASSERT( ! is_seed());
-        RYML_ASSERT(valid());
-        size_t ch = m_tree->find_child(m_id, k);
-        RYML_ASSERT(ch != NONE);
-        NodeRef const r(m_tree, ch);
-        return r;
-    }
-
-    /** O(num_children) */
-    NodeRef operator[] (size_t pos)
-    {
-        RYML_ASSERT( ! is_seed());
-        RYML_ASSERT(valid());
-        size_t ch = m_tree->child(m_id, pos);
-        NodeRef r = ch != NONE ? NodeRef(m_tree, ch) : NodeRef(m_tree, m_id, pos);
-        return r;
-    }
-
-    /** O(num_children) */
-    NodeRef const operator[] (size_t pos) const
-    {
-        RYML_ASSERT( ! is_seed());
-        RYML_ASSERT(valid());
-        size_t ch = m_tree->child(m_id, pos);
-        RYML_ASSERT(ch != NONE);
-        NodeRef const r(m_tree, ch);
-        return r;
-    }
-
-    /** @} */
-
-public:
-
-    /** node modification */
-    /** @{ */
-
     void create() { _apply_seed(); }
 
     inline void operator= (NodeType_e t)
@@ -428,6 +800,9 @@ class RYML_EXPORT NodeRef
 
 public:
 
+    /** @name serialization */
+    /** @{ */
+
     /** serialize a variable to the arena */
     template<class T>
     inline csubstr to_arena(T const& C4_RESTRICT s) const
@@ -455,21 +830,6 @@ class RYML_EXPORT NodeRef
         return *this;
     }
 
-    template<class T>
-    inline NodeRef const& operator>> (T &v) const
-    {
-        RYML_ASSERT( ! is_seed());
-        RYML_ASSERT(valid());
-        RYML_ASSERT(get() != nullptr);
-        if( ! read(*this, &v))
-        {
-            c4::yml::error("could not deserialize value");
-        }
-        return *this;
-    }
-
-public:
-
     /** serialize a variable, then assign the result to the node's key */
     template<class T>
     inline NodeRef& operator<< (Key<const T> const& C4_RESTRICT v)
@@ -488,19 +848,6 @@ class RYML_EXPORT NodeRef
         return *this;
     }
 
-    /** deserialize the node's key to the given variable */
-    template<class T>
-    inline NodeRef const& operator>> (Key<T> v) const
-    {
-        RYML_ASSERT( ! is_seed());
-        RYML_ASSERT(valid());
-        RYML_ASSERT(get() != nullptr);
-        from_chars(key(), &v.k);
-        return *this;
-    }
-
-public:
-
     NodeRef& operator<< (Key<fmt::const_base64_wrapper> w)
     {
         set_key_serialized(w.wrapper);
@@ -513,43 +860,7 @@ class RYML_EXPORT NodeRef
         return *this;
     }
 
-    NodeRef const& operator>> (Key<fmt::base64_wrapper> w) const
-    {
-        deserialize_key(w.wrapper);
-        return *this;
-    }
-
-    NodeRef const& operator>> (fmt::base64_wrapper w) const
-    {
-        deserialize_val(w);
-        return *this;
-    }
-
-public:
-
-    template<class T>
-    void get_if(csubstr name, T *var) const
-    {
-        auto ch = find_child(name);
-        if(ch.valid())
-        {
-            ch >> *var;
-        }
-    }
-
-    template<class T>
-    void get_if(csubstr name, T *var, T fallback) const
-    {
-        auto ch = find_child(name);
-        if(ch.valid())
-        {
-            ch >> *var;
-        }
-        else
-        {
-            *var = fallback;
-        }
-    }
+    /** @} */
 
 private:
 
@@ -593,6 +904,9 @@ class RYML_EXPORT NodeRef
 
 public:
 
+    /** @name modification of hierarchy */
+    /** @{ */
+
     inline NodeRef insert_child(NodeRef after)
     {
         _C4RV();
@@ -642,7 +956,7 @@ class RYML_EXPORT NodeRef
 
 public:
 
-    inline NodeRef insert_sibling(NodeRef const after)
+    inline NodeRef insert_sibling(ConstNodeRef const& after)
     {
         _C4RV();
         RYML_ASSERT(after.m_tree == m_tree);
@@ -650,7 +964,7 @@ class RYML_EXPORT NodeRef
         return r;
     }
 
-    inline NodeRef insert_sibling(NodeInit const& i, NodeRef const after)
+    inline NodeRef insert_sibling(NodeInit const& i, ConstNodeRef const& after)
     {
         _C4RV();
         RYML_ASSERT(after.m_tree == m_tree);
@@ -722,7 +1036,7 @@ class RYML_EXPORT NodeRef
 public:
 
     /** change the node's position within its parent */
-    inline void move(NodeRef const after)
+    inline void move(ConstNodeRef const& after)
     {
         _C4RV();
         m_tree->move(m_id, after.m_id);
@@ -731,7 +1045,7 @@ class RYML_EXPORT NodeRef
     /** move the node to a different parent, which may belong to a different
      * tree. When this is the case, then this node's tree pointer is reset to
      * the tree of the parent node. */
-    inline void move(NodeRef const parent, NodeRef const after)
+    inline void move(NodeRef const& parent, ConstNodeRef const& after)
     {
         _C4RV();
         RYML_ASSERT(parent.m_tree == after.m_tree);
@@ -746,7 +1060,7 @@ class RYML_EXPORT NodeRef
         }
     }
 
-    inline NodeRef duplicate(NodeRef const parent, NodeRef const after) const
+    inline NodeRef duplicate(NodeRef const& parent, ConstNodeRef const& after) const
     {
         _C4RV();
         RYML_ASSERT(parent.m_tree == after.m_tree);
@@ -764,7 +1078,7 @@ class RYML_EXPORT NodeRef
         }
     }
 
-    inline void duplicate_children(NodeRef const parent, NodeRef const after) const
+    inline void duplicate_children(NodeRef const& parent, ConstNodeRef const& after) const
     {
         _C4RV();
         RYML_ASSERT(parent.m_tree == after.m_tree);
@@ -778,61 +1092,21 @@ class RYML_EXPORT NodeRef
         }
     }
 
-private:
-
-    template<class Nd>
-    struct child_iterator
-    {
-        Tree * m_tree;
-        size_t m_child_id;
-
-        using value_type = NodeRef;
-
-        child_iterator(Tree * t, size_t id) : m_tree(t), m_child_id(id) {}
-
-        child_iterator& operator++ () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->next_sibling(m_child_id); return *this; }
-        child_iterator& operator-- () { RYML_ASSERT(m_child_id != NONE); m_child_id = m_tree->prev_sibling(m_child_id); return *this; }
+    /** @} */
 
-        Nd operator*  () const { return Nd(m_tree, m_child_id); }
-        Nd operator-> () const { return Nd(m_tree, m_child_id); }
+public:
 
-        bool operator!= (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id != that.m_child_id; }
-        bool operator== (child_iterator that) const { RYML_ASSERT(m_tree == that.m_tree); return m_child_id == that.m_child_id; }
-    };
+    /** @name iteration */
+    /** @{ */
 
-public:
+    using       iterator = detail::child_iterator<     NodeRef>;
 
-    using       iterator = child_iterator<      NodeRef>;
-    using const_iterator = child_iterator<const NodeRef>;
+    using       children_view = detail::children_view_<     NodeRef>;
 
     inline iterator begin() { return iterator(m_tree, m_tree->first_child(m_id)); }
     inline iterator end  () { return iterator(m_tree, NONE); }
 
-    inline const_iterator begin() const { return const_iterator(m_tree, m_tree->first_child(m_id)); }
-    inline const_iterator end  () const { return const_iterator(m_tree, NONE); }
-
-private:
-
-    template<class Nd>
-    struct children_view_
-    {
-        using n_iterator = child_iterator<Nd>;
-
-        n_iterator b, e;
-
-        inline children_view_(n_iterator const& b_, n_iterator const& e_) : b(b_), e(e_) {}
-
-        inline n_iterator begin() const { return b; }
-        inline n_iterator end  () const { return e; }
-    };
-
-public:
-
-    using       children_view = children_view_<      NodeRef>;
-    using const_children_view = children_view_<const NodeRef>;
-
           children_view children()       { return       children_view(begin(), end()); }
-    const_children_view children() const { return const_children_view(begin(), end()); }
 
     #if defined(__clang__)
     #   pragma clang diagnostic push
@@ -844,8 +1118,7 @@ class RYML_EXPORT NodeRef
     #   endif
     #endif
 
-          children_view siblings()       { if(is_root()) { return       children_view(end(), end()); } else { size_t p = get()->m_parent; return       children_view(iterator(m_tree, m_tree->get(p)->m_first_child), iterator(m_tree, NONE)); } }
-    const_children_view siblings() const { if(is_root()) { return const_children_view(end(), end()); } else { size_t p = get()->m_parent; return const_children_view(const_iterator(m_tree, m_tree->get(p)->m_first_child), const_iterator(m_tree, NONE)); } }
+    children_view siblings() { if(is_root()) { return children_view(end(), end()); } else { size_t p = get()->m_parent; return       children_view(iterator(m_tree, m_tree->get(p)->m_first_child), iterator(m_tree, NONE)); } }
 
     #if defined(__clang__)
     #   pragma clang diagnostic pop
@@ -853,110 +1126,90 @@ class RYML_EXPORT NodeRef
     #   pragma GCC diagnostic pop
     #endif
 
-public:
-
     /** visit every child node calling fn(node) */
-    template<class Visitor> bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true);
-    /** visit every child node calling fn(node) */
-    template<class Visitor> bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const;
-
-    /** visit every child node calling fn(node, level) */
-    template<class Visitor> bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true);
+    template<class Visitor>
+    bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true)
+    {
+        return detail::_visit(*this, fn, indentation_level, skip_root);
+    }
     /** visit every child node calling fn(node, level) */
-    template<class Visitor> bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const;
+    template<class Visitor>
+    bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true)
+    {
+        return detail::_visit_stacked(*this, fn, indentation_level, skip_root);
+    }
+
+    /** @} */
 
 #undef _C4RV
 };
 
+
 //-----------------------------------------------------------------------------
-template<class T>
-inline void write(NodeRef *n, T const& v)
+
+inline ConstNodeRef::ConstNodeRef(NodeRef const& that)
+    : m_tree(that.m_tree)
+    , m_id(!that.is_seed() ? that.id() : NONE)
 {
-    n->set_val_serialized(v);
 }
 
-template<class T>
-typename std::enable_if< ! std::is_floating_point<T>::value, bool>::type
-inline read(NodeRef const& n, T *v)
+inline ConstNodeRef::ConstNodeRef(NodeRef && that)
+    : m_tree(that.m_tree)
+    , m_id(!that.is_seed() ? that.id() : NONE)
 {
-    return from_chars(n.val(), v);
 }
 
-template<class T>
-typename std::enable_if< std::is_floating_point<T>::value, bool>::type
-inline read(NodeRef const& n, T *v)
+
+inline ConstNodeRef& ConstNodeRef::operator= (NodeRef const& that)
 {
-    return from_chars_float(n.val(), v);
+    m_tree = (that.m_tree);
+    m_id = (!that.is_seed() ? that.id() : NONE);
+    return *this;
+}
+
+inline ConstNodeRef& ConstNodeRef::operator= (NodeRef && that)
+{
+    m_tree = (that.m_tree);
+    m_id = (!that.is_seed() ? that.id() : NONE);
+    return *this;
 }
 
 
 //-----------------------------------------------------------------------------
-template<class Visitor>
-bool NodeRef::visit(Visitor fn, size_t indentation_level, bool skip_root)
+
+template<class T>
+inline void write(NodeRef *n, T const& v)
 {
-    return const_cast<NodeRef const*>(this)->visit(fn, indentation_level, skip_root);
+    n->set_val_serialized(v);
 }
 
-template<class Visitor>
-bool NodeRef::visit(Visitor fn, size_t indentation_level, bool skip_root) const
+template<class T>
+typename std::enable_if< ! std::is_floating_point<T>::value, bool>::type
+inline read(NodeRef const& n, T *v)
 {
-    size_t increment = 0;
-    if( ! (is_root() && skip_root))
-    {
-        if(fn(this, indentation_level))
-        {
-            return true;
-        }
-        ++increment;
-    }
-    if(has_children())
-    {
-        for(auto ch : children())
-        {
-            if(ch.visit(fn, indentation_level + increment)) // no need to forward skip_root as it won't be root
-            {
-                return true;
-            }
-        }
-    }
-    return false;
+    return from_chars(n.val(), v);
 }
-
-
-template<class Visitor>
-bool NodeRef::visit_stacked(Visitor fn, size_t indentation_level, bool skip_root)
+template<class T>
+typename std::enable_if< ! std::is_floating_point<T>::value, bool>::type
+inline read(ConstNodeRef const& n, T *v)
 {
-    return const_cast< NodeRef const* >(this)->visit_stacked(fn, indentation_level, skip_root);
+    return from_chars(n.val(), v);
 }
 
-template<class Visitor>
-bool NodeRef::visit_stacked(Visitor fn, size_t indentation_level, bool skip_root) const
+template<class T>
+typename std::enable_if<std::is_floating_point<T>::value, bool>::type
+inline read(NodeRef const& n, T *v)
 {
-    size_t increment = 0;
-    if( ! (is_root() && skip_root))
-    {
-        if(fn(this, indentation_level))
-        {
-            return true;
-        }
-        ++increment;
-    }
-    if(has_children())
-    {
-        fn.push(this, indentation_level);
-        for(auto ch : children())
-        {
-            if(ch.visit(fn, indentation_level + increment)) // no need to forward skip_root as it won't be root
-            {
-                fn.pop(this, indentation_level);
-                return true;
-            }
-        }
-        fn.pop(this, indentation_level);
-    }
-    return false;
+    return from_chars_float(n.val(), v);
+}
+template<class T>
+typename std::enable_if<std::is_floating_point<T>::value, bool>::type
+inline read(ConstNodeRef const& n, T *v)
+{
+    return from_chars_float(n.val(), v);
 }
 
+
 } // namespace yml
 } // namespace c4
 
diff --git a/src/c4/yml/parse.cpp b/src/c4/yml/parse.cpp
index 8fdc88cf2..fd369fb7f 100644
--- a/src/c4/yml/parse.cpp
+++ b/src/c4/yml/parse.cpp
@@ -5186,7 +5186,7 @@ csubstr Parser::location_contents(Location const& loc) const
     return m_buf.sub(loc.offset);
 }
 
-Location Parser::location(NodeRef node) const
+Location Parser::location(ConstNodeRef node) const
 {
     _RYML_CB_ASSERT(m_stack.m_callbacks, node.valid());
     return location(*node.tree(), node.id());
diff --git a/src/c4/yml/parse.hpp b/src/c4/yml/parse.hpp
index 9e38db497..ee457664a 100644
--- a/src/c4/yml/parse.hpp
+++ b/src/c4/yml/parse.hpp
@@ -236,7 +236,7 @@ class RYML_EXPORT Parser
     /** Get the location of a node of the last tree to be parsed by this parser. */
     Location location(Tree const& tree, size_t node_id) const;
     /** Get the location of a node of the last tree to be parsed by this parser. */
-    Location location(NodeRef node) const;
+    Location location(ConstNodeRef node) const;
     /** Get the string starting at a particular location, to the end
      * of the parsed source buffer. */
     csubstr location_contents(Location const& loc) const;
diff --git a/src/c4/yml/std/map.hpp b/src/c4/yml/std/map.hpp
index 5d60bda63..fc48dc5e6 100644
--- a/src/c4/yml/std/map.hpp
+++ b/src/c4/yml/std/map.hpp
@@ -26,7 +26,7 @@ void write(c4::yml::NodeRef *n, std::map<K, V, Less, Alloc> const& m)
 }
 
 template<class K, class V, class Less, class Alloc>
-bool read(c4::yml::NodeRef const& n, std::map<K, V, Less, Alloc> * m)
+bool read(c4::yml::ConstNodeRef const& n, std::map<K, V, Less, Alloc> * m)
 {
     K k{};
     V v{};
diff --git a/src/c4/yml/std/vector.hpp b/src/c4/yml/std/vector.hpp
index f28007336..1b6a4610a 100644
--- a/src/c4/yml/std/vector.hpp
+++ b/src/c4/yml/std/vector.hpp
@@ -22,7 +22,7 @@ void write(c4::yml::NodeRef *n, std::vector<V, Alloc> const& vec)
 }
 
 template<class V, class Alloc>
-bool read(c4::yml::NodeRef const& n, std::vector<V, Alloc> *vec)
+bool read(c4::yml::ConstNodeRef const& n, std::vector<V, Alloc> *vec)
 {
     vec->resize(n.num_children());
     size_t pos = 0;
@@ -34,7 +34,7 @@ bool read(c4::yml::NodeRef const& n, std::vector<V, Alloc> *vec)
 /** specialization: std::vector<bool> uses std::vector<bool>::reference as
  * the return value of its operator[]. */
 template<class Alloc>
-bool read(c4::yml::NodeRef const& n, std::vector<bool, Alloc> *vec)
+bool read(c4::yml::ConstNodeRef const& n, std::vector<bool, Alloc> *vec)
 {
     vec->resize(n.num_children());
     size_t pos = 0;
diff --git a/src/c4/yml/tree.cpp b/src/c4/yml/tree.cpp
index 51f469d4b..aeee5df24 100644
--- a/src/c4/yml/tree.cpp
+++ b/src/c4/yml/tree.cpp
@@ -240,9 +240,18 @@ NodeRef Tree::rootref()
 {
     return NodeRef(this, root_id());
 }
-NodeRef const Tree::rootref() const
+ConstNodeRef Tree::rootref() const
 {
-    return NodeRef(const_cast<Tree*>(this), root_id());
+    return ConstNodeRef(this, root_id());
+}
+
+ConstNodeRef Tree::crootref()
+{
+    return ConstNodeRef(this, root_id());
+}
+ConstNodeRef Tree::crootref() const
+{
+    return ConstNodeRef(this, root_id());
 }
 
 NodeRef Tree::ref(size_t id)
@@ -250,17 +259,28 @@ NodeRef Tree::ref(size_t id)
     _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size);
     return NodeRef(this, id);
 }
-NodeRef const Tree::ref(size_t id) const
+ConstNodeRef Tree::ref(size_t id) const
 {
     _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size);
-    return NodeRef(const_cast<Tree*>(this), id);
+    return ConstNodeRef(this, id);
+}
+
+ConstNodeRef Tree::cref(size_t id)
+{
+    _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size);
+    return ConstNodeRef(this, id);
+}
+ConstNodeRef Tree::cref(size_t id) const
+{
+    _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size);
+    return ConstNodeRef(this, id);
 }
 
 NodeRef Tree::operator[] (csubstr key)
 {
     return rootref()[key];
 }
-NodeRef const Tree::operator[] (csubstr key) const
+ConstNodeRef Tree::operator[] (csubstr key) const
 {
     return rootref()[key];
 }
@@ -269,7 +289,7 @@ NodeRef Tree::operator[] (size_t i)
 {
     return rootref()[i];
 }
-NodeRef const Tree::operator[] (size_t i) const
+ConstNodeRef Tree::operator[] (size_t i) const
 {
     return rootref()[i];
 }
@@ -278,9 +298,9 @@ NodeRef Tree::docref(size_t i)
 {
     return ref(doc(i));
 }
-NodeRef const Tree::docref(size_t i) const
+ConstNodeRef Tree::docref(size_t i) const
 {
-    return ref(doc(i));
+    return cref(doc(i));
 }
 
 
@@ -1537,9 +1557,7 @@ size_t Tree::num_children(size_t node) const
 {
     size_t count = 0;
     for(size_t i = first_child(node); i != NONE; i = next_sibling(i))
-    {
         ++count;
-    }
     return count;
 }
 
diff --git a/src/c4/yml/tree.hpp b/src/c4/yml/tree.hpp
index 43f08cf58..3255c387b 100644
--- a/src/c4/yml/tree.hpp
+++ b/src/c4/yml/tree.hpp
@@ -27,6 +27,7 @@ struct NodeScalar;
 struct NodeInit;
 struct NodeData;
 class NodeRef;
+class ConstNodeRef;
 class Tree;
 
 
@@ -205,9 +206,6 @@ struct NodeType
 
 public:
 
-    C4_ALWAYS_INLINE operator NodeType_e      & C4_RESTRICT ()       { return type; }
-    C4_ALWAYS_INLINE operator NodeType_e const& C4_RESTRICT () const { return type; }
-
     C4_ALWAYS_INLINE NodeType() : type(NOTYPE) {}
     C4_ALWAYS_INLINE NodeType(NodeType_e t) : type(t) {}
     C4_ALWAYS_INLINE NodeType(type_bits t) : type((NodeType_e)t) {}
@@ -226,6 +224,14 @@ struct NodeType
 
     C4_ALWAYS_INLINE void clear() { type = NOTYPE; }
 
+public:
+
+    C4_ALWAYS_INLINE operator NodeType_e      & C4_RESTRICT ()       { return type; }
+    C4_ALWAYS_INLINE operator NodeType_e const& C4_RESTRICT () const { return type; }
+
+    C4_ALWAYS_INLINE bool operator== (NodeType_e t) const { return type == t; }
+    C4_ALWAYS_INLINE bool operator!= (NodeType_e t) const { return type != t; }
+
 public:
 
     #if defined(__clang__)
@@ -238,6 +244,7 @@ struct NodeType
     #   endif
     #endif
 
+    C4_ALWAYS_INLINE bool is_notype() const { return type == NOTYPE; }
     C4_ALWAYS_INLINE bool is_stream() const { return ((type & STREAM) == STREAM) != 0; }
     C4_ALWAYS_INLINE bool is_doc() const { return (type & DOC) != 0; }
     C4_ALWAYS_INLINE bool is_container() const { return (type & (MAP|SEQ|STREAM)) != 0; }
@@ -524,35 +531,43 @@ class RYML_EXPORT Tree
     size_t root_id() const {                                 RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; }
 
     //! Get a NodeRef of a node by id
-    NodeRef       ref(size_t id);
+    NodeRef      ref(size_t id);
+    //! Get a NodeRef of a node by id
+    ConstNodeRef ref(size_t id) const;
     //! Get a NodeRef of a node by id
-    NodeRef const ref(size_t id) const;
+    ConstNodeRef cref(size_t id);
+    //! Get a NodeRef of a node by id
+    ConstNodeRef cref(size_t id) const;
 
     //! Get the root as a NodeRef
-    NodeRef       rootref();
+    NodeRef      rootref();
+    //! Get the root as a NodeRef
+    ConstNodeRef rootref() const;
+    //! Get the root as a NodeRef
+    ConstNodeRef crootref();
     //! Get the root as a NodeRef
-    NodeRef const rootref() const;
+    ConstNodeRef crootref() const;
 
     //! find a root child by name, return it as a NodeRef
     //! @note requires the root to be a map.
-    NodeRef       operator[] (csubstr key);
+    NodeRef      operator[] (csubstr key);
     //! find a root child by name, return it as a NodeRef
     //! @note requires the root to be a map.
-    NodeRef const operator[] (csubstr key) const;
+    ConstNodeRef operator[] (csubstr key) const;
 
     //! find a root child by index: return the root node's @p i-th child as a NodeRef
     //! @note @i is NOT the node id, but the child's position
-    NodeRef       operator[] (size_t i);
+    NodeRef      operator[] (size_t i);
     //! find a root child by index: return the root node's @p i-th child as a NodeRef
     //! @note @i is NOT the node id, but the child's position
-    NodeRef const operator[] (size_t i) const;
+    ConstNodeRef operator[] (size_t i) const;
 
     //! get the i-th document of the stream
     //! @note @i is NOT the node id, but the doc position within the stream
-    NodeRef       docref(size_t i);
+    NodeRef      docref(size_t i);
     //! get the i-th document of the stream
     //! @note @i is NOT the node id, but the doc position within the stream
-    NodeRef const docref(size_t i) const;
+    ConstNodeRef docref(size_t i) const;
 
     /** @} */
 


Test patch:
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index a6b5a44a0..cc68cec8d 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -63,6 +63,7 @@ ryml_add_test(callbacks)
 ryml_add_test(stack)
 ryml_add_test(parser)
 ryml_add_test(tree)
+ryml_add_test(noderef)
 ryml_add_test(emit)
 ryml_add_test(style)
 ryml_add_test(serialize)
diff --git a/test/test_basic.cpp b/test/test_basic.cpp
index 878e4341e..56e580296 100644
--- a/test/test_basic.cpp
+++ b/test/test_basic.cpp
@@ -238,15 +238,15 @@ User: ed
 )";
     test_check_emit_check(yaml, [](Tree const &t){
         ASSERT_TRUE(t.rootref().is_stream());
-        const NodeRef doc0 = t.rootref()[0];
+        ConstNodeRef doc0 = t.rootref()[0];
         EXPECT_EQ(doc0["Time"].val(), csubstr("2001-11-23 15:01:42 -5"));
         EXPECT_EQ(doc0["User"].val(), csubstr("ed"));
         EXPECT_EQ(doc0["Warning"].val(), csubstr("This is an error message for the log file"));
-        const NodeRef doc1 = t.rootref()[1];
+        ConstNodeRef doc1 = t.rootref()[1];
         EXPECT_EQ(doc1["Time"].val(), csubstr("2001-11-23 15:02:31 -5"));
         EXPECT_EQ(doc1["User"].val(), csubstr("ed"));
         EXPECT_EQ(doc1["Warning"].val(), csubstr("A slightly different error message."));
-        const NodeRef doc2 = t.rootref()[2];
+        ConstNodeRef doc2 = t.rootref()[2];
         EXPECT_EQ(doc2["Date"].val(), csubstr("2001-11-23 15:03:17 -5"));
         EXPECT_EQ(doc2["User"].val(), csubstr("ed"));
         EXPECT_EQ(doc2["Fatal"].val(), csubstr("Unknown variable \"bar\""));
diff --git a/test/test_block_folded.cpp b/test/test_block_folded.cpp
index be2ad7025..bdfd54750 100644
--- a/test/test_block_folded.cpp
+++ b/test/test_block_folded.cpp
@@ -164,7 +164,7 @@ TEST(block_folded, issue152_indented_once)
       exec pg_isready -U "dog" -d "dbname=dog" -h 127.0.0.1 -p 5432
   err3_parses: no
 )");
-    const NodeRef n = t["indented_once"];
+    ConstNodeRef n = t["indented_once"];
     EXPECT_EQ(n["ok"  ][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
     EXPECT_EQ(n["err" ][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
     EXPECT_EQ(n["err2"][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
@@ -194,7 +194,7 @@ TEST(block_folded, issue152_indented_twice)
         exec pg_isready -U "dog" -d "dbname=dog" -h 127.0.0.1 -p 5432
     err3_parses: no
 )");
-    const NodeRef n = t["indented_once"]["indented_twice"];
+    ConstNodeRef n = t["indented_once"]["indented_twice"];
     EXPECT_EQ(n["ok"  ][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
     EXPECT_EQ(n["err" ][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
     EXPECT_EQ(n["err2"][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
@@ -225,7 +225,7 @@ TEST(block_folded, issue152_indented_thrice)
           exec pg_isready -U "dog" -d "dbname=dog" -h 127.0.0.1 -p 5432
       err3_parses: no
 )");
-    const NodeRef n = t["indented_once"]["indented_twice"]["indented_thrice"];
+    ConstNodeRef n = t["indented_once"]["indented_twice"]["indented_thrice"];
     EXPECT_EQ(n["ok"  ][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
     EXPECT_EQ(n["err" ][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
     EXPECT_EQ(n["err2"][0].val(), csubstr("exec pg_isready -U \"dog\" -d \"dbname=dog\" -h 127.0.0.1 -p 5432\n"));
@@ -283,7 +283,7 @@ TEST(block_folded, test_suite_4QFQ_pt2)
 )";
     test_check_emit_check(yaml, [](Tree const &t){
         ASSERT_TRUE(t.rootref().is_stream());
-        NodeRef doc = t.rootref().child(0);
+        ConstNodeRef doc = t.rootref().child(0);
         ASSERT_TRUE(doc.is_seq());
         ASSERT_EQ(doc.num_children(), 4u);
         EXPECT_EQ(doc[0].val(), csubstr("child0\n"));
@@ -644,7 +644,7 @@ TEST(block_folded, test_suite_W4TN)
 # empty
 )";
     test_check_emit_check(yaml, [](Tree const &t){
-        NodeRef const r = t.rootref();
+        ConstNodeRef r = t.rootref();
         ASSERT_TRUE(r.is_stream());
         ASSERT_EQ(r.num_children(), 10u);
         ASSERT_TRUE(r.doc(0).is_doc());
diff --git a/test/test_case.cpp b/test/test_case.cpp
index cacf20e54..5317e2d15 100644
--- a/test/test_case.cpp
+++ b/test/test_case.cpp
@@ -332,7 +332,7 @@ NodeType_e CaseNode::_guess() const
 
 
 //-----------------------------------------------------------------------------
-void CaseNode::compare_child(yml::NodeRef const& n, size_t pos) const
+void CaseNode::compare_child(yml::ConstNodeRef const& n, size_t pos) const
 {
     EXPECT_TRUE(pos < n.num_children());
     EXPECT_TRUE(pos < children.size());
@@ -412,7 +412,7 @@ void CaseNode::compare_child(yml::NodeRef const& n, size_t pos) const
     }
 }
 
-void CaseNode::compare(yml::NodeRef const& actual, bool ignore_quote) const
+void CaseNode::compare(yml::ConstNodeRef const& actual, bool ignore_quote) const
 {
     if(ignore_quote)
     {
@@ -499,11 +499,11 @@ void CaseNode::recreate(yml::NodeRef *n) const
 
 //-----------------------------------------------------------------------------
 
-void print_path(NodeRef const& n)
+void print_path(ConstNodeRef const& n)
 {
     size_t len = 0;
     char buf[1024];
-    NodeRef p = n;
+    ConstNodeRef p = n;
     while(p != nullptr)
     {
         if(p.has_key())
@@ -611,10 +611,10 @@ void print_node(CaseNode const& p, int level)
 }
 
 
-void print_tree(NodeRef const& p, int level)
+void print_tree(ConstNodeRef const& p, int level)
 {
     print_node(p, level);
-    for(NodeRef const ch : p.children())
+    for(ConstNodeRef ch : p.children())
     {
         print_tree(ch, level+1);
     }
@@ -635,7 +635,7 @@ void print_tree(CaseNode const& t)
     printf("--------------------------------------\n");
 }
 
-void test_invariants(NodeRef const n)
+void test_invariants(ConstNodeRef const& n)
 {
     if(n.is_root())
     {
@@ -657,7 +657,7 @@ void test_invariants(NodeRef const n)
         EXPECT_FALSE(n.is_val());
     }
     // check parent & sibling reciprocity
-    for(NodeRef const s : n.siblings())
+    for(ConstNodeRef s : n.siblings())
     {
         EXPECT_TRUE(n.has_sibling(s));
         EXPECT_TRUE(s.has_sibling(n));
@@ -682,7 +682,7 @@ void test_invariants(NodeRef const n)
     {
         EXPECT_TRUE(n.is_container());
         EXPECT_FALSE(n.is_map());
-        for(NodeRef const ch : n.children())
+        for(ConstNodeRef ch : n.children())
         {
             EXPECT_FALSE(ch.is_keyval());
             EXPECT_FALSE(ch.has_key());
@@ -692,7 +692,7 @@ void test_invariants(NodeRef const n)
     {
         EXPECT_TRUE(n.is_container());
         EXPECT_FALSE(n.is_seq());
-        for(NodeRef const ch : n.children())
+        for(ConstNodeRef ch : n.children())
         {
             EXPECT_TRUE(ch.has_key());
         }
@@ -720,13 +720,13 @@ void test_invariants(NodeRef const n)
     // ... add more tests here
 
     // now recurse into the children
-    for(NodeRef const ch : n.children())
+    for(ConstNodeRef ch : n.children())
     {
         test_invariants(ch);
     }
 }
 
-size_t test_tree_invariants(NodeRef const& n)
+size_t test_tree_invariants(ConstNodeRef const& n)
 {
     auto parent = n.parent();
 
@@ -756,7 +756,7 @@ size_t test_tree_invariants(NodeRef const& n)
     }
 
     size_t count = 1, num = 0;
-    for(NodeRef const ch : n.children())
+    for(ConstNodeRef ch : n.children())
     {
         EXPECT_NE(ch.id(), n.id());
         count += test_tree_invariants(ch);
diff --git a/test/test_case.hpp b/test/test_case.hpp
index fd38e00b3..60967df66 100644
--- a/test/test_case.hpp
+++ b/test/test_case.hpp
@@ -39,6 +39,15 @@ inline void PrintTo(csubstr s, ::std::ostream* os) { os->write(s.str, (std::stre
 
 namespace yml {
 
+inline void PrintTo(NodeType ty, ::std::ostream* os)
+{
+    *os << ty.type_str();
+}
+inline void PrintTo(NodeType_e ty, ::std::ostream* os)
+{
+    *os << NodeType::type_str(ty);
+}
+
 inline void PrintTo(Callbacks const& cb, ::std::ostream* os)
 {
 #ifdef __GNUC__
@@ -69,11 +78,11 @@ void test_compare(Tree const& actual, size_t node_actual,
 void test_arena_not_shared(Tree const& a, Tree const& b);
 
 void test_invariants(Tree const& t);
-void test_invariants(NodeRef const n);
+void test_invariants(ConstNodeRef const& n);
 
 void print_node(CaseNode const& t, int level=0);
 void print_tree(CaseNode const& p, int level=0);
-void print_path(NodeRef const& p);
+void print_path(ConstNodeRef const& p);
 
 
 
@@ -413,8 +422,8 @@ struct CaseNode
 
 public:
 
-    void compare(yml::NodeRef const& n, bool ignore_quote=false) const;
-    void compare_child(yml::NodeRef const& n, size_t pos) const;
+    void compare(yml::ConstNodeRef const& n, bool ignore_quote=false) const;
+    void compare_child(yml::ConstNodeRef const& n, size_t pos) const;
 
     size_t reccount() const
     {
diff --git a/test/test_emit.cpp b/test/test_emit.cpp
index 05a56b428..50c581ca5 100644
--- a/test/test_emit.cpp
+++ b/test/test_emit.cpp
@@ -114,7 +114,7 @@ void test_emits(Tree const& t, std::string const& expected, std::string const& e
 }
 
 template<class ...Args>
-void test_emits(const NodeRef t, std::string const& expected, std::string const& expected_json)
+void test_emits(ConstNodeRef t, std::string const& expected, std::string const& expected_json)
 {
     EXPECT_EQ(emit2buf([&](substr buf){ return emit(t, buf); }), expected);
     EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, buf); }), expected_json);
@@ -151,7 +151,7 @@ TEST(emit, existing_seq_node)
         std::string expected_json = R"(["foo","bar",["nested","seq"],{"nested": "map"}])";
         {
             SCOPED_TRACE("rootref");
-            test_emits(t.rootref(), expected, expected_json);
+            test_emits(t.crootref(), expected, expected_json);
         }
         {
             SCOPED_TRACE("t");
@@ -163,7 +163,7 @@ TEST(emit, existing_seq_node)
         }
     }
     {
-        const NodeRef n = t[0];
+        ConstNodeRef n = t[0];
         std::string expected = "foo\n";
         std::string expected_json = "\"foo\"";
         {
@@ -186,7 +186,7 @@ TEST(emit, existing_seq_node)
         }
     }
     {
-        const NodeRef n = t[1];
+        ConstNodeRef n = t[1];
         std::string expected = "bar\n";
         std::string expected_json = "\"bar\"";
         {
@@ -210,7 +210,7 @@ TEST(emit, existing_seq_node)
 
     }
     {
-        const NodeRef n = t[2];
+        ConstNodeRef n = t[2];
         std::string expected = "- nested\n- seq\n";
         std::string expected_json = "[\"nested\",\"seq\"]";
         {
@@ -233,7 +233,7 @@ TEST(emit, existing_seq_node)
         }
     }
     {
-        const NodeRef n = t[3];
+        ConstNodeRef n = t[3];
         std::string expected = "nested: map\n";
         std::string expected_json = "{\"nested\": \"map\"}";
         {
@@ -278,7 +278,7 @@ TEST(emit, existing_map_node)
         }
     }
     {
-        const NodeRef n = t[0];
+        ConstNodeRef n = t[0];
         std::string expected = "0: foo\n";
         std::string expected_json = "\"0\": \"foo\"";
         {
@@ -301,7 +301,7 @@ TEST(emit, existing_map_node)
         }
     }
     {
-        const NodeRef n = t[1];
+        ConstNodeRef n = t[1];
         std::string expected = "1: bar\n";
         std::string expected_json = "\"1\": \"bar\"";
         {
@@ -324,7 +324,7 @@ TEST(emit, existing_map_node)
         }
     }
     {
-        const NodeRef n = t[2];
+        ConstNodeRef n = t[2];
         std::string expected = "2:\n  - nested\n  - seq\n";
         std::string expected_json = "\"2\": [\"nested\",\"seq\"]";
         {
@@ -347,7 +347,7 @@ TEST(emit, existing_map_node)
         }
     }
     {
-        const NodeRef n = t[3];
+        ConstNodeRef n = t[3];
         std::string expected = "3:\n  nested: map\n";
         std::string expected_json = "\"3\": {\"nested\": \"map\"}";
         {
diff --git a/test/test_location.cpp b/test/test_location.cpp
index 1b10c6f6b..a99d70425 100644
--- a/test/test_location.cpp
+++ b/test/test_location.cpp
@@ -66,7 +66,7 @@ TEST(locations, seq_block)
       - yet another val
 )";
     Tree t = parser.parse_in_arena("myfile.yml", yaml);
-    const NodeRef seq = t.rootref();
+    ConstNodeRef seq = t.rootref();
     ASSERT_TRUE(seq.is_seq());
     _checkloc(seq         ,  1u, 0u, "- ");
     _checkloc(seq[0]      ,  1u, 2u, "this");
@@ -105,7 +105,7 @@ map: ~
     hah: here
 )";
     Tree t = parser.parse_in_arena("myfile.yml", yaml);
-    const NodeRef map = t.rootref();
+    ConstNodeRef map = t.rootref();
     ASSERT_TRUE(map.is_map());
     _checkloc(map                         ,  1u, 0u, "this:");
     _checkloc(map["this"]                 ,  1u, 0u, "this:");
@@ -282,7 +282,7 @@ TEST(locations, seq_flow)
     Parser parser;
     csubstr yaml = R"([one,two,three,four,items])";
     parser.parse_in_arena("myfile.yml", yaml, &t);
-    const NodeRef seq = t.rootref();
+    ConstNodeRef seq = t.rootref();
     ASSERT_TRUE(seq.is_seq());
     _checkloc(seq   ,  0u,  0u, "[");
     _checkloc(seq[0],  0u,  1u, "one");
@@ -298,7 +298,7 @@ TEST(locations, map_flow)
     Parser parser;
     csubstr yaml = R"({one: item,two: items,three: items,four: items})";
     parser.parse_in_arena("myfile.yml", yaml, &t);
-    const NodeRef map = t.rootref();
+    ConstNodeRef map = t.rootref();
     ASSERT_TRUE(map.is_map());
     _checkloc(map   ,  0u,  0u, "{");
     _checkloc(map[0],  0u,  1u, "one:");
@@ -322,7 +322,7 @@ TEST(locations, seq_flow_nested)
   was
 ])";
     parser.parse_in_arena("myfile.yml", yaml, &t);
-    const NodeRef seq = t.rootref();
+    ConstNodeRef seq = t.rootref();
     ASSERT_TRUE(seq.is_seq());
     _checkloc(seq                      ,  0u,  0u, "[");
     _checkloc(seq[0]                   ,  1u,  2u, "one");
@@ -374,8 +374,8 @@ foo: yes
     -     3_
 )";
     parser.parse_in_arena("myfile.yml", yaml, &t);
-    const NodeRef stream = t.rootref();
-    const NodeRef map = t.docref(0);
+    ConstNodeRef stream = t.rootref();
+    ConstNodeRef map = t.docref(0);
     ASSERT_TRUE(map.is_map());
     ASSERT_TRUE(map.is_doc());
     _checkloc(stream       , 0u, 0u, "---");
@@ -462,7 +462,7 @@ foo6: definitely  # 56
     -     3_
 )";
     parser.parse_in_arena("myfile.yml", yaml, &t);
-    const NodeRef map = t.docref(0);
+    ConstNodeRef map = t.docref(0);
     ASSERT_TRUE(map.is_map());
     ASSERT_TRUE(map.is_doc());
     _checkloc(t.rootref()   ,     0u, 0u, "---");
diff --git a/test/test_noderef.cpp b/test/test_noderef.cpp
new file mode 100644
index 000000000..3680d1fed
--- /dev/null
+++ b/test/test_noderef.cpp
@@ -0,0 +1,427 @@
+#ifndef RYML_SINGLE_HEADER
+#include "c4/yml/std/std.hpp"
+#include "c4/yml/parse.hpp"
+#include "c4/yml/emit.hpp"
+#include <c4/format.hpp>
+#include <c4/yml/detail/checks.hpp>
+#include <c4/yml/detail/print.hpp>
+#endif
+#include "./test_case.hpp"
+#include "./callbacks_tester.hpp"
+
+#include <gtest/gtest.h>
+
+#if defined(_MSC_VER)
+#   pragma warning(push)
+#   pragma warning(disable: 4389) // signed/unsigned mismatch
+#elif defined(__clang__)
+#   pragma clang diagnostic push
+#   pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
+#elif defined(__GNUC__)
+#   pragma GCC diagnostic push
+#endif
+
+namespace c4 {
+namespace yml {
+
+TEST(NodeRef, general)
+{
+    Tree t;
+
+    NodeRef root(&t);
+
+    //using S = csubstr;
+    //using V = NodeScalar;
+    using N = NodeInit;
+
+    root = N{MAP};
+    root.append_child({"a", "0"});
+    root.append_child({MAP, "b"});
+    root["b"].append_child({SEQ, "seq"});
+    root["b"]["seq"].append_child({"0"});
+    root["b"]["seq"].append_child({"1"});
+    root["b"]["seq"].append_child({"2"});
+    root["b"]["seq"].append_child({NodeScalar{"!!str", "3"}});
+    auto ch4 = root["b"]["seq"][3].append_sibling({"4"});
+    EXPECT_EQ(ch4.id(), root["b"]["seq"][4].id());
+    EXPECT_EQ(ch4.get(), root["b"]["seq"][4].get());
+    EXPECT_EQ((type_bits)root["b"]["seq"][4].type(), (type_bits)VAL);
+    EXPECT_EQ(root["b"]["seq"][4].val(), "4");
+    root["b"]["seq"].append_sibling({NodeScalar{"!!str", "aaa"}, NodeScalar{"!!int", "0"}});
+    EXPECT_EQ((type_bits)root["b"]["seq"][4].type(), (type_bits)VAL);
+    EXPECT_EQ(root["b"]["seq"][4].val(), "4");
+
+    root["b"]["key"] = "val";
+    auto seq = root["b"]["seq"];
+    auto seq2 = root["b"]["seq2"];
+    EXPECT_TRUE(seq2.is_seed());
+    root["b"]["seq2"] = N(SEQ);
+    seq2 = root["b"]["seq2"];
+    EXPECT_FALSE(seq2.is_seed());
+    EXPECT_TRUE(seq2.is_seq());
+    EXPECT_EQ(seq2.num_children(), 0);
+    EXPECT_EQ(root["b"]["seq2"].get(), seq2.get());
+    auto seq20 = seq2[0];
+    EXPECT_TRUE(seq20.is_seed());
+    EXPECT_TRUE(seq2[0].is_seed());
+    EXPECT_EQ(seq2.num_children(), 0);
+    EXPECT_TRUE(seq2[0].is_seed());
+    EXPECT_TRUE(seq20.is_seed());
+    EXPECT_NE(seq.get(), seq2.get());
+    seq20 = root["b"]["seq2"][0];
+    EXPECT_TRUE(seq20.is_seed());
+    root["b"]["seq2"][0] = "00";
+    seq20 = root["b"]["seq2"][0];
+    EXPECT_FALSE(seq20.is_seed());
+    NodeRef before = root["b"]["key"];
+    EXPECT_EQ(before.key(), "key");
+    EXPECT_EQ(before.val(), "val");
+    root["b"]["seq2"][1] = "01";
+    NodeRef after = root["b"]["key"];
+    EXPECT_EQ(before.key(), "key");
+    EXPECT_EQ(before.val(), "val");
+    EXPECT_EQ(after.key(), "key");
+    EXPECT_EQ(after.val(), "val");
+    root["b"]["seq2"][2] = "02";
+    root["b"]["seq2"][3] = "03";
+    int iv = 0;
+    root["b"]["seq2"][4] << 55; root["b"]["seq2"][4] >> iv;
+    EXPECT_EQ(iv, 55);
+    size_t zv = 0;
+    root["b"]["seq2"][5] << size_t(55); root["b"]["seq2"][5] >> zv;
+    EXPECT_EQ(zv, size_t(55));
+    float fv = 0;
+    root["b"]["seq2"][6] << 2.0f; root["b"]["seq2"][6] >> fv;
+    EXPECT_EQ(fv, 2.f);
+    float dv = 0;
+    root["b"]["seq2"][7] << 2.0; root["b"]["seq2"][7] >> dv;
+    EXPECT_EQ(dv, 2.0);
+
+    EXPECT_EQ(root["b"]["key"].key(), "key");
+    EXPECT_EQ(root["b"]["key"].val(), "val");
+
+
+    emit(t);
+
+    EXPECT_TRUE(root.type().is_map());
+    EXPECT_TRUE(root["a"].type().is_keyval());
+    EXPECT_EQ(root["a"].key(), "a");
+    EXPECT_EQ(root["a"].val(), "0");
+
+    EXPECT_TRUE(root["b"].type().has_key());
+    EXPECT_TRUE(root["b"].type().is_map());
+
+    EXPECT_TRUE(root["b"]["seq"].type().has_key());
+    EXPECT_TRUE(root["b"]["seq"].type().is_seq());
+    EXPECT_EQ  (root["b"]["seq"].key(), "seq");
+    EXPECT_TRUE(root["b"]["seq"][0].type().is_val());
+    EXPECT_EQ(  root["b"]["seq"][0].val(), "0");
+    EXPECT_TRUE(root["b"]["seq"][1].type().is_val());
+    EXPECT_EQ(  root["b"]["seq"][1].val(), "1");
+    EXPECT_TRUE(root["b"]["seq"][2].type().is_val());
+    EXPECT_EQ(  root["b"]["seq"][2].val(), "2");
+    EXPECT_TRUE(root["b"]["seq"][3].type().is_val());
+    EXPECT_EQ(  root["b"]["seq"][3].val(), "3");
+    EXPECT_EQ(  root["b"]["seq"][3].val_tag(), "!!str");
+    EXPECT_TRUE(root["b"]["seq"][4].type().is_val());
+    EXPECT_EQ(  root["b"]["seq"][4].val(), "4");
+
+    int tv;
+    EXPECT_EQ(root["b"]["key"].key(), "key");
+    EXPECT_EQ(root["b"]["key"].val(), "val");
+    EXPECT_EQ(root["b"]["seq2"][0].val(), "00"); root["b"]["seq2"][0] >> tv; EXPECT_EQ(tv, 0);
+    EXPECT_EQ(root["b"]["seq2"][1].val(), "01"); root["b"]["seq2"][1] >> tv; EXPECT_EQ(tv, 1);
+    EXPECT_EQ(root["b"]["seq2"][2].val(), "02"); root["b"]["seq2"][2] >> tv; EXPECT_EQ(tv, 2);
+    EXPECT_EQ(root["b"]["seq2"][3].val(), "03"); root["b"]["seq2"][3] >> tv; EXPECT_EQ(tv, 3);
+    EXPECT_EQ(root["b"]["seq2"][4].val(), "55"); EXPECT_EQ(iv, 55);
+    EXPECT_EQ(root["b"]["seq2"][5].val(), "55"); EXPECT_EQ(zv, size_t(55));
+    EXPECT_EQ(root["b"]["seq2"][6].val(), "2"); EXPECT_EQ(fv, 2.f);
+    EXPECT_EQ(root["b"]["seq2"][6].val(), "2"); EXPECT_EQ(dv, 2.);
+
+    root["b"]["seq"][2].set_val_serialized(22);
+
+    emit(t);
+
+    EXPECT_TRUE(root["b"]["aaa"].type().is_keyval());
+    EXPECT_EQ(root["b"]["aaa"].key_tag(), "!!str");
+    EXPECT_EQ(root["b"]["aaa"].key(), "aaa");
+    EXPECT_EQ(root["b"]["aaa"].val_tag(), "!!int");
+    EXPECT_EQ(root["b"]["aaa"].val(), "0");
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+void noderef_check_tree(ConstNodeRef const& root)
+{
+    test_invariants(*root.tree());
+
+    EXPECT_EQ(root.tree()->size(), 7u);
+    EXPECT_EQ(root.num_children(), 6u);
+    EXPECT_EQ(root.is_container(), true);
+    EXPECT_EQ(root.is_seq(), true);
+
+    EXPECT_TRUE(root[0].type().is_val());
+    EXPECT_EQ(  root[0].val(), "0");
+    EXPECT_TRUE(root[1].type().is_val());
+    EXPECT_EQ(  root[1].val(), "1");
+    EXPECT_TRUE(root[2].type().is_val());
+    EXPECT_EQ(  root[2].val(), "2");
+    EXPECT_TRUE(root[3].type().is_val());
+    EXPECT_EQ(  root[3].val(), "3");
+    EXPECT_TRUE(root[4].type().is_val());
+    EXPECT_EQ(  root[4].val(), "4");
+    EXPECT_TRUE(root[5].type().is_val());
+    EXPECT_EQ(  root[5].val(), "5");
+}
+
+TEST(NodeRef, append_child)
+{
+    Tree t;
+
+    NodeRef root(&t);
+
+    root |= SEQ;
+    root.append_child({"0"});
+    root.append_child({"1"});
+    root.append_child({"2"});
+    root.append_child({"3"});
+    root.append_child({"4"});
+    root.append_child({"5"});
+
+    noderef_check_tree(root);
+}
+
+TEST(NodeRef, prepend_child)
+{
+    Tree t;
+
+    NodeRef root(&t);
+
+    root |= SEQ;
+    root.prepend_child({"5"});
+    root.prepend_child({"4"});
+    root.prepend_child({"3"});
+    root.prepend_child({"2"});
+    root.prepend_child({"1"});
+    root.prepend_child({"0"});
+
+    noderef_check_tree(root);
+}
+
+TEST(NodeRef, insert_child)
+{
+    Tree t;
+
+    NodeRef root(&t);
+    NodeRef none(&t, NONE);
+
+    root |= SEQ;
+    root.insert_child({"3"}, none);
+    root.insert_child({"4"}, root[0]);
+    root.insert_child({"0"}, none);
+    root.insert_child({"5"}, root[2]);
+    root.insert_child({"1"}, root[0]);
+    root.insert_child({"2"}, root[1]);
+
+    noderef_check_tree(root);
+}
+
+TEST(NodeRef, remove_child)
+{
+    Tree t;
+
+    NodeRef root(&t);
+    NodeRef none(&t, NONE);
+
+    root |= SEQ;
+    root.insert_child({"3"}, none);
+    root.insert_child({"4"}, root[0]);
+    root.insert_child({"0"}, none);
+    root.insert_child({"5"}, root[2]);
+    root.insert_child({"1"}, root[0]);
+    root.insert_child({"2"}, root[1]);
+
+    std::vector<int> vec({10, 20, 30, 40, 50, 60, 70, 80, 90});
+    root.insert_child(root[0]) << vec; // 1
+    root.insert_child(root[2]) << vec; // 3
+    root.insert_child(root[4]) << vec; // 5
+    root.insert_child(root[6]) << vec; // 7
+    root.insert_child(root[8]) << vec; // 9
+    root.append_child() << vec;        // 10
+
+    root.remove_child(11);
+    root.remove_child(9);
+    root.remove_child(7);
+    root.remove_child(5);
+    root.remove_child(3);
+    root.remove_child(1);
+
+    noderef_check_tree(root);
+
+    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
+    root.prepend_child() << vec2; // 0
+    root.insert_child(root[1]) << vec2; // 2
+    root.insert_child(root[3]) << vec2; // 4
+    root.insert_child(root[5]) << vec2; // 6
+    root.insert_child(root[7]) << vec2; // 8
+    root.insert_child(root[9]) << vec2; // 10
+    root.append_child() << vec2;        // 12
+
+    root.remove_child(12);
+    root.remove_child(10);
+    root.remove_child(8);
+    root.remove_child(6);
+    root.remove_child(4);
+    root.remove_child(2);
+    root.remove_child(0);
+
+    noderef_check_tree(root);
+}
+
+TEST(NodeRef, move_in_same_parent)
+{
+    Tree t;
+    NodeRef r = t;
+
+    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
+    std::map<std::string, int> map2({{"foo", 100}, {"bar", 200}, {"baz", 300}});
+
+    r |= SEQ;
+    r.append_child() << vec2;
+    r.append_child() << map2;
+    r.append_child() << "elm2";
+    r.append_child() << "elm3";
+
+    auto s = r[0];
+    auto m = r[1];
+    EXPECT_TRUE(s.is_seq());
+    EXPECT_TRUE(m.is_map());
+    EXPECT_EQ(s.num_children(), vec2.size());
+    EXPECT_EQ(m.num_children(), map2.size());
+    //printf("fonix"); print_tree(t); emit(r);
+    r[0].move(r[1]);
+    //printf("fonix"); print_tree(t); emit(r);
+    EXPECT_EQ(r[0].get(), m.get());
+    EXPECT_EQ(r[0].num_children(), map2.size());
+    EXPECT_EQ(r[1].get(), s.get());
+    EXPECT_EQ(r[1].num_children(), vec2.size());
+}
+
+TEST(NodeRef, move_to_other_parent)
+{
+    Tree t;
+    NodeRef r = t;
+
+    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
+    std::map<std::string, int> map2({{"foo", 100}, {"bar", 200}, {"baz", 300}});
+
+    r |= SEQ;
+    r.append_child() << vec2;
+    r.append_child() << map2;
+    r.append_child() << "elm2";
+    r.append_child() << "elm3";
+
+    NodeData *elm2 = r[2].get();
+    EXPECT_EQ(r[2].val(), "elm2");
+    //printf("fonix"); print_tree(t); emit(r);
+    r[2].move(r[0], r[0][0]);
+    EXPECT_EQ(r[0][1].get(), elm2);
+    EXPECT_EQ(r[0][1].val(), "elm2");
+    //printf("fonix"); print_tree(t); emit(r);
+}
+
+TEST(NodeRef, duplicate)
+{
+    Tree t;
+    NodeRef r = t;
+
+    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
+    std::map<std::string, int> map2({{"bar", 200}, {"baz", 300}, {"foo", 100}});
+
+    r |= SEQ;
+    r.append_child() << vec2;
+    r.append_child() << map2;
+    r.append_child() << "elm2";
+    r.append_child() << "elm3";
+
+    EXPECT_EQ(r[0][0].num_children(), 2u);
+    NodeRef dup = r[1].duplicate(r[0][0], r[0][0][1]);
+    EXPECT_EQ(r[0][0].num_children(), 3u);
+    EXPECT_EQ(r[0][0][2].num_children(), map2.size());
+    EXPECT_NE(dup.get(), r[1].get());
+    EXPECT_EQ(dup[0].key(), "bar");
+    EXPECT_EQ(dup[0].val(), "200");
+    EXPECT_EQ(dup[1].key(), "baz");
+    EXPECT_EQ(dup[1].val(), "300");
+    EXPECT_EQ(dup[2].key(), "foo");
+    EXPECT_EQ(dup[2].val(), "100");
+    EXPECT_EQ(dup[0].key().str, r[1][0].key().str);
+    EXPECT_EQ(dup[0].val().str, r[1][0].val().str);
+    EXPECT_EQ(dup[0].key().len, r[1][0].key().len);
+    EXPECT_EQ(dup[0].val().len, r[1][0].val().len);
+    EXPECT_EQ(dup[1].key().str, r[1][1].key().str);
+    EXPECT_EQ(dup[1].val().str, r[1][1].val().str);
+    EXPECT_EQ(dup[1].key().len, r[1][1].key().len);
+    EXPECT_EQ(dup[1].val().len, r[1][1].val().len);
+}
+
+TEST(NodeRef, intseq)
+{
+    Tree t = parse_in_arena("iseq: [8, 10]");
+    NodeRef n = t["iseq"];
+    int a, b;
+    n[0] >> a;
+    n[1] >> b;
+    EXPECT_EQ(a, 8);
+    EXPECT_EQ(b, 10);
+}
+
+TEST(NodeRef, vsConstNodeRef)
+{
+    Tree t = parse_in_arena("iseq: [8, 10]");
+    Tree const& ct = t;
+    NodeRef mseq = t["iseq"];
+    ConstNodeRef seq = t["iseq"];
+    EXPECT_EQ(mseq.tree(), seq.tree());
+    EXPECT_EQ(mseq.id(), seq.id());
+    EXPECT_TRUE(mseq == seq);
+    EXPECT_FALSE(mseq != seq);
+    EXPECT_TRUE(seq == mseq);
+    EXPECT_FALSE(seq != mseq);
+    // mseq = ct["iseq"]; // deliberate compile error
+    seq = ct["iseq"]; // ok
+    // mseq = seq; // deliberate compilation error
+    seq = mseq; // ok
+    {
+        NodeData *nd = mseq.get();
+        // nd = seq.get(); // deliberate compile error
+        C4_UNUSED(nd);
+    }
+    {
+        NodeData const* nd = seq.get();
+        nd = seq.get(); // ok
+        C4_UNUSED(nd);
+    }
+}
+
+
+//-------------------------------------------
+// this is needed to use the test case library
+Case const* get_case(csubstr /*name*/)
+{
+    return nullptr;
+}
+
+} // namespace yml
+} // namespace c4
+
+#if defined(_MSC_VER)
+#   pragma warning(pop)
+#elif defined(__clang__)
+#   pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#   pragma GCC diagnostic pop
+#endif
diff --git a/test/test_plain_scalar.cpp b/test/test_plain_scalar.cpp
index 8dee65026..ec147c5d8 100644
--- a/test/test_plain_scalar.cpp
+++ b/test/test_plain_scalar.cpp
@@ -29,7 +29,7 @@ word1
 )";
     test_check_emit_check(yaml, [](Tree const &t){
         EXPECT_TRUE(t.rootref().is_stream());
-        NodeRef doc = t.rootref().first_child();
+        ConstNodeRef doc = t.rootref().first_child();
         ASSERT_TRUE(doc.is_doc());
         ASSERT_TRUE(doc.is_val());
         EXPECT_EQ(doc.val(), "word1");
diff --git a/test/test_suite.cpp b/test/test_suite.cpp
index 67be33f77..566ff4d63 100644
--- a/test/test_suite.cpp
+++ b/test/test_suite.cpp
@@ -200,9 +200,9 @@ struct ProcLevel
 
     void _resolve_if_needed()
     {
-        const NodeRef root = tree.rootref();
-        bool has_anchors_or_refs = root.visit([](NodeRef const* node, size_t /*level*/){
-            return (node->is_anchor() || node->is_ref());
+        ConstNodeRef root = tree.rootref();
+        bool has_anchors_or_refs = root.visit([](ConstNodeRef const& node, size_t /*level*/){
+            return (node.is_anchor() || node.is_ref());
         });
         if(has_anchors_or_refs)
         {
diff --git a/test/test_tree.cpp b/test/test_tree.cpp
index 19893f8d3..c7e2dddc4 100644
--- a/test/test_tree.cpp
+++ b/test/test_tree.cpp
@@ -1059,9 +1059,9 @@ foo: bar
     const size_t stream_id = t.root_id();
     const size_t doc_id = t.first_child(stream_id);
     const size_t keyval_id = t.first_child(doc_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
     EXPECT_TRUE(t.is_stream(stream_id));
     EXPECT_FALSE(t.is_stream(doc_id));
     EXPECT_FALSE(t.is_stream(keyval_id));
@@ -1093,10 +1093,14 @@ a scalar
     const size_t doc_id = t.first_child(stream_id);
     const size_t keyval_id = t.first_child(doc_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.is_doc(stream_id));
     EXPECT_TRUE(t.is_doc(doc_id));
     EXPECT_FALSE(t.is_doc(keyval_id));
@@ -1105,6 +1109,10 @@ a scalar
     EXPECT_TRUE(doc.is_doc());
     EXPECT_FALSE(keyval.is_doc());
     EXPECT_TRUE(docval.is_doc());
+    EXPECT_FALSE(mstream.is_doc());
+    EXPECT_TRUE(mdoc.is_doc());
+    EXPECT_FALSE(mkeyval.is_doc());
+    EXPECT_TRUE(mdocval.is_doc());
     EXPECT_EQ(t.is_doc(stream_id), t._p(stream_id)->m_type.is_doc());
     EXPECT_EQ(t.is_doc(doc_id), t._p(doc_id)->m_type.is_doc());
     EXPECT_EQ(t.is_doc(keyval_id), t._p(keyval_id)->m_type.is_doc());
@@ -1113,6 +1121,10 @@ a scalar
     EXPECT_EQ(doc.is_doc(), doc.get()->m_type.is_doc());
     EXPECT_EQ(keyval.is_doc(), keyval.get()->m_type.is_doc());
     EXPECT_EQ(docval.is_doc(), docval.get()->m_type.is_doc());
+    EXPECT_EQ(mstream.is_doc(), mstream.get()->m_type.is_doc());
+    EXPECT_EQ(mdoc.is_doc(), mdoc.get()->m_type.is_doc());
+    EXPECT_EQ(mkeyval.is_doc(), mkeyval.get()->m_type.is_doc());
+    EXPECT_EQ(mdocval.is_doc(), mdocval.get()->m_type.is_doc());
 }
 
 TEST(NodeType, is_container)
@@ -1144,13 +1156,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_TRUE(t.is_container(stream_id));
     EXPECT_TRUE(t.is_container(doc_id));
     EXPECT_TRUE(t.is_container(map_id));
@@ -1165,6 +1184,13 @@ a scalar
     EXPECT_TRUE(seq.is_container());
     EXPECT_FALSE(val.is_container());
     EXPECT_FALSE(docval.is_container());
+    EXPECT_TRUE(mstream.is_container());
+    EXPECT_TRUE(mdoc.is_container());
+    EXPECT_TRUE(mmap.is_container());
+    EXPECT_FALSE(mkeyval.is_container());
+    EXPECT_TRUE(mseq.is_container());
+    EXPECT_FALSE(mval.is_container());
+    EXPECT_FALSE(mdocval.is_container());
     EXPECT_EQ(t.is_container(stream_id), t._p(stream_id)->m_type.is_container());
     EXPECT_EQ(t.is_container(doc_id), t._p(doc_id)->m_type.is_container());
     EXPECT_EQ(t.is_container(map_id), t._p(map_id)->m_type.is_container());
@@ -1179,6 +1205,13 @@ a scalar
     EXPECT_EQ(seq.is_container(), seq.get()->m_type.is_container());
     EXPECT_EQ(val.is_container(), val.get()->m_type.is_container());
     EXPECT_EQ(docval.is_container(), docval.get()->m_type.is_container());
+    EXPECT_EQ(mstream.is_container(), mstream.get()->m_type.is_container());
+    EXPECT_EQ(mdoc.is_container(), mdoc.get()->m_type.is_container());
+    EXPECT_EQ(mmap.is_container(), mmap.get()->m_type.is_container());
+    EXPECT_EQ(mkeyval.is_container(), mkeyval.get()->m_type.is_container());
+    EXPECT_EQ(mseq.is_container(), mseq.get()->m_type.is_container());
+    EXPECT_EQ(mval.is_container(), mval.get()->m_type.is_container());
+    EXPECT_EQ(mdocval.is_container(), mdocval.get()->m_type.is_container());
 }
 
 TEST(NodeType, is_map)
@@ -1207,13 +1240,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.is_map(stream_id));
     EXPECT_TRUE(t.is_map(doc_id));
     EXPECT_TRUE(t.is_map(map_id));
@@ -1228,6 +1268,13 @@ a scalar
     EXPECT_FALSE(seq.is_map());
     EXPECT_FALSE(val.is_map());
     EXPECT_FALSE(docval.is_map());
+    EXPECT_FALSE(mstream.is_map());
+    EXPECT_TRUE(mdoc.is_map());
+    EXPECT_TRUE(mmap.is_map());
+    EXPECT_FALSE(mkeyval.is_map());
+    EXPECT_FALSE(mseq.is_map());
+    EXPECT_FALSE(mval.is_map());
+    EXPECT_FALSE(mdocval.is_map());
     EXPECT_EQ(t.is_map(stream_id), t._p(stream_id)->m_type.is_map());
     EXPECT_EQ(t.is_map(doc_id), t._p(doc_id)->m_type.is_map());
     EXPECT_EQ(t.is_map(map_id), t._p(map_id)->m_type.is_map());
@@ -1242,6 +1289,13 @@ a scalar
     EXPECT_EQ(seq.is_map(), seq.get()->m_type.is_map());
     EXPECT_EQ(val.is_map(), val.get()->m_type.is_map());
     EXPECT_EQ(docval.is_map(), docval.get()->m_type.is_map());
+    EXPECT_EQ(mstream.is_map(), mstream.get()->m_type.is_map());
+    EXPECT_EQ(mdoc.is_map(), mdoc.get()->m_type.is_map());
+    EXPECT_EQ(mmap.is_map(), mmap.get()->m_type.is_map());
+    EXPECT_EQ(mkeyval.is_map(), mkeyval.get()->m_type.is_map());
+    EXPECT_EQ(mseq.is_map(), mseq.get()->m_type.is_map());
+    EXPECT_EQ(mval.is_map(), mval.get()->m_type.is_map());
+    EXPECT_EQ(mdocval.is_map(), mdocval.get()->m_type.is_map());
 }
 
 TEST(NodeType, is_seq)
@@ -1270,13 +1324,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_TRUE(t.is_seq(stream_id));
     EXPECT_FALSE(t.is_seq(doc_id));
     EXPECT_FALSE(t.is_seq(map_id));
@@ -1291,6 +1352,13 @@ a scalar
     EXPECT_TRUE(seq.is_seq());
     EXPECT_FALSE(val.is_seq());
     EXPECT_FALSE(docval.is_seq());
+    EXPECT_TRUE(mstream.is_seq());
+    EXPECT_FALSE(mdoc.is_seq());
+    EXPECT_FALSE(mmap.is_seq());
+    EXPECT_FALSE(mkeyval.is_seq());
+    EXPECT_TRUE(mseq.is_seq());
+    EXPECT_FALSE(mval.is_seq());
+    EXPECT_FALSE(mdocval.is_seq());
     EXPECT_EQ(t.is_seq(stream_id), t._p(stream_id)->m_type.is_seq());
     EXPECT_EQ(t.is_seq(doc_id), t._p(doc_id)->m_type.is_seq());
     EXPECT_EQ(t.is_seq(map_id), t._p(map_id)->m_type.is_seq());
@@ -1305,6 +1373,13 @@ a scalar
     EXPECT_EQ(seq.is_seq(), seq.get()->m_type.is_seq());
     EXPECT_EQ(val.is_seq(), val.get()->m_type.is_seq());
     EXPECT_EQ(docval.is_seq(), docval.get()->m_type.is_seq());
+    EXPECT_EQ(mstream.is_seq(), mstream.get()->m_type.is_seq());
+    EXPECT_EQ(mdoc.is_seq(), mdoc.get()->m_type.is_seq());
+    EXPECT_EQ(mmap.is_seq(), mmap.get()->m_type.is_seq());
+    EXPECT_EQ(mkeyval.is_seq(), mkeyval.get()->m_type.is_seq());
+    EXPECT_EQ(mseq.is_seq(), mseq.get()->m_type.is_seq());
+    EXPECT_EQ(mval.is_seq(), mval.get()->m_type.is_seq());
+    EXPECT_EQ(mdocval.is_seq(), mdocval.get()->m_type.is_seq());
 }
 
 TEST(NodeType, has_val)
@@ -1333,13 +1408,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.has_val(stream_id));
     EXPECT_FALSE(t.has_val(doc_id));
     EXPECT_FALSE(t.has_val(map_id));
@@ -1354,6 +1436,13 @@ a scalar
     EXPECT_FALSE(seq.has_val());
     EXPECT_TRUE(val.has_val());
     EXPECT_TRUE(docval.has_val());
+    EXPECT_FALSE(mstream.has_val());
+    EXPECT_FALSE(mdoc.has_val());
+    EXPECT_FALSE(mmap.has_val());
+    EXPECT_TRUE(mkeyval.has_val());
+    EXPECT_FALSE(mseq.has_val());
+    EXPECT_TRUE(mval.has_val());
+    EXPECT_TRUE(mdocval.has_val());
     EXPECT_EQ(t.has_val(stream_id), t._p(stream_id)->m_type.has_val());
     EXPECT_EQ(t.has_val(doc_id), t._p(doc_id)->m_type.has_val());
     EXPECT_EQ(t.has_val(map_id), t._p(map_id)->m_type.has_val());
@@ -1368,6 +1457,13 @@ a scalar
     EXPECT_EQ(seq.has_val(), seq.get()->m_type.has_val());
     EXPECT_EQ(val.has_val(), val.get()->m_type.has_val());
     EXPECT_EQ(docval.has_val(), docval.get()->m_type.has_val());
+    EXPECT_EQ(mstream.has_val(), mstream.get()->m_type.has_val());
+    EXPECT_EQ(mdoc.has_val(), mdoc.get()->m_type.has_val());
+    EXPECT_EQ(mmap.has_val(), mmap.get()->m_type.has_val());
+    EXPECT_EQ(mkeyval.has_val(), mkeyval.get()->m_type.has_val());
+    EXPECT_EQ(mseq.has_val(), mseq.get()->m_type.has_val());
+    EXPECT_EQ(mval.has_val(), mval.get()->m_type.has_val());
+    EXPECT_EQ(mdocval.has_val(), mdocval.get()->m_type.has_val());
 }
 
 TEST(NodeType, is_val)
@@ -1396,13 +1492,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.is_val(stream_id));
     EXPECT_FALSE(t.is_val(doc_id));
     EXPECT_FALSE(t.is_val(map_id));
@@ -1417,6 +1520,13 @@ a scalar
     EXPECT_FALSE(seq.is_val());
     EXPECT_TRUE(val.is_val());
     EXPECT_TRUE(docval.is_val());
+    EXPECT_FALSE(mstream.is_val());
+    EXPECT_FALSE(mdoc.is_val());
+    EXPECT_FALSE(mmap.is_val());
+    EXPECT_FALSE(mkeyval.is_val());
+    EXPECT_FALSE(mseq.is_val());
+    EXPECT_TRUE(mval.is_val());
+    EXPECT_TRUE(mdocval.is_val());
     EXPECT_EQ(t.is_val(stream_id), t._p(stream_id)->m_type.is_val());
     EXPECT_EQ(t.is_val(doc_id), t._p(doc_id)->m_type.is_val());
     EXPECT_EQ(t.is_val(map_id), t._p(map_id)->m_type.is_val());
@@ -1431,6 +1541,13 @@ a scalar
     EXPECT_EQ(seq.is_val(), seq.get()->m_type.is_val());
     EXPECT_EQ(val.is_val(), val.get()->m_type.is_val());
     EXPECT_EQ(docval.is_val(), docval.get()->m_type.is_val());
+    EXPECT_EQ(mstream.is_val(), mstream.get()->m_type.is_val());
+    EXPECT_EQ(mdoc.is_val(), mdoc.get()->m_type.is_val());
+    EXPECT_EQ(mmap.is_val(), mmap.get()->m_type.is_val());
+    EXPECT_EQ(mkeyval.is_val(), mkeyval.get()->m_type.is_val());
+    EXPECT_EQ(mseq.is_val(), mseq.get()->m_type.is_val());
+    EXPECT_EQ(mval.is_val(), mval.get()->m_type.is_val());
+    EXPECT_EQ(mdocval.is_val(), mdocval.get()->m_type.is_val());
 }
 
 TEST(NodeType, has_key)
@@ -1458,13 +1575,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.has_key(stream_id));
     EXPECT_FALSE(t.has_key(doc_id));
     EXPECT_TRUE(t.has_key(map_id));
@@ -1479,6 +1603,13 @@ a scalar
     EXPECT_TRUE(seq.has_key());
     EXPECT_FALSE(val.has_key());
     EXPECT_FALSE(docval.has_key());
+    EXPECT_FALSE(mstream.has_key());
+    EXPECT_FALSE(mdoc.has_key());
+    EXPECT_TRUE(mmap.has_key());
+    EXPECT_TRUE(mkeyval.has_key());
+    EXPECT_TRUE(mseq.has_key());
+    EXPECT_FALSE(mval.has_key());
+    EXPECT_FALSE(mdocval.has_key());
     EXPECT_EQ(t.has_key(stream_id), t._p(stream_id)->m_type.has_key());
     EXPECT_EQ(t.has_key(doc_id), t._p(doc_id)->m_type.has_key());
     EXPECT_EQ(t.has_key(map_id), t._p(map_id)->m_type.has_key());
@@ -1493,6 +1624,13 @@ a scalar
     EXPECT_EQ(seq.has_key(), seq.get()->m_type.has_key());
     EXPECT_EQ(val.has_key(), val.get()->m_type.has_key());
     EXPECT_EQ(docval.has_key(), docval.get()->m_type.has_key());
+    EXPECT_EQ(mstream.has_key(), mstream.get()->m_type.has_key());
+    EXPECT_EQ(mdoc.has_key(), mdoc.get()->m_type.has_key());
+    EXPECT_EQ(mmap.has_key(), mmap.get()->m_type.has_key());
+    EXPECT_EQ(mkeyval.has_key(), mkeyval.get()->m_type.has_key());
+    EXPECT_EQ(mseq.has_key(), mseq.get()->m_type.has_key());
+    EXPECT_EQ(mval.has_key(), mval.get()->m_type.has_key());
+    EXPECT_EQ(mdocval.has_key(), mdocval.get()->m_type.has_key());
 }
 
 TEST(NodeType, is_keyval)
@@ -1521,13 +1659,20 @@ a scalar
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.is_keyval(stream_id));
     EXPECT_FALSE(t.is_keyval(doc_id));
     EXPECT_FALSE(t.is_keyval(map_id));
@@ -1542,6 +1687,13 @@ a scalar
     EXPECT_FALSE(seq.is_keyval());
     EXPECT_FALSE(val.is_keyval());
     EXPECT_FALSE(docval.is_keyval());
+    EXPECT_FALSE(mstream.is_keyval());
+    EXPECT_FALSE(mdoc.is_keyval());
+    EXPECT_FALSE(mmap.is_keyval());
+    EXPECT_TRUE(mkeyval.is_keyval());
+    EXPECT_FALSE(mseq.is_keyval());
+    EXPECT_FALSE(mval.is_keyval());
+    EXPECT_FALSE(mdocval.is_keyval());
     EXPECT_EQ(t.is_keyval(stream_id), t._p(stream_id)->m_type.is_keyval());
     EXPECT_EQ(t.is_keyval(doc_id), t._p(doc_id)->m_type.is_keyval());
     EXPECT_EQ(t.is_keyval(map_id), t._p(map_id)->m_type.is_keyval());
@@ -1556,6 +1708,13 @@ a scalar
     EXPECT_EQ(seq.is_keyval(), seq.get()->m_type.is_keyval());
     EXPECT_EQ(val.is_keyval(), val.get()->m_type.is_keyval());
     EXPECT_EQ(docval.is_keyval(), docval.get()->m_type.is_keyval());
+    EXPECT_EQ(mstream.is_keyval(), mstream.get()->m_type.is_keyval());
+    EXPECT_EQ(mdoc.is_keyval(), mdoc.get()->m_type.is_keyval());
+    EXPECT_EQ(mmap.is_keyval(), mmap.get()->m_type.is_keyval());
+    EXPECT_EQ(mkeyval.is_keyval(), mkeyval.get()->m_type.is_keyval());
+    EXPECT_EQ(mseq.is_keyval(), mseq.get()->m_type.is_keyval());
+    EXPECT_EQ(mval.is_keyval(), mval.get()->m_type.is_keyval());
+    EXPECT_EQ(mdocval.is_keyval(), mdocval.get()->m_type.is_keyval());
 }
 
 TEST(NodeType, has_key_tag)
@@ -1582,15 +1741,24 @@ a scalar
     const size_t val_id = t.first_child(seq_id);
     const size_t valnotag_id = t.last_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnotag = t.ref(keyvalnotag_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnotag = t.ref(val_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnotag = t.ref(keyvalnotag_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnotag = t.ref(val_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnotag = t.ref(keyvalnotag_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnotag = t.ref(val_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.has_key_tag(stream_id));
     EXPECT_FALSE(t.has_key_tag(doc_id));
     EXPECT_TRUE(t.has_key_tag(map_id));
@@ -1609,6 +1777,15 @@ a scalar
     EXPECT_FALSE(val.has_key_tag());
     EXPECT_FALSE(valnotag.has_key_tag());
     EXPECT_FALSE(docval.has_key_tag());
+    EXPECT_FALSE(mstream.has_key_tag());
+    EXPECT_FALSE(mdoc.has_key_tag());
+    EXPECT_TRUE(mmap.has_key_tag());
+    EXPECT_TRUE(mkeyval.has_key_tag());
+    EXPECT_FALSE(mkeyvalnotag.has_key_tag());
+    EXPECT_TRUE(mseq.has_key_tag());
+    EXPECT_FALSE(mval.has_key_tag());
+    EXPECT_FALSE(mvalnotag.has_key_tag());
+    EXPECT_FALSE(mdocval.has_key_tag());
     EXPECT_EQ(t.has_key_tag(stream_id), t._p(stream_id)->m_type.has_key_tag());
     EXPECT_EQ(t.has_key_tag(doc_id), t._p(doc_id)->m_type.has_key_tag());
     EXPECT_EQ(t.has_key_tag(map_id), t._p(map_id)->m_type.has_key_tag());
@@ -1627,6 +1804,15 @@ a scalar
     EXPECT_EQ(val.has_key_tag(), val.get()->m_type.has_key_tag());
     EXPECT_EQ(valnotag.has_key_tag(), valnotag.get()->m_type.has_key_tag());
     EXPECT_EQ(docval.has_key_tag(), docval.get()->m_type.has_key_tag());
+    EXPECT_EQ(mstream.has_key_tag(), mstream.get()->m_type.has_key_tag());
+    EXPECT_EQ(mdoc.has_key_tag(), mdoc.get()->m_type.has_key_tag());
+    EXPECT_EQ(mmap.has_key_tag(), mmap.get()->m_type.has_key_tag());
+    EXPECT_EQ(mkeyval.has_key_tag(), mkeyval.get()->m_type.has_key_tag());
+    EXPECT_EQ(mkeyvalnotag.has_key_tag(), mkeyvalnotag.get()->m_type.has_key_tag());
+    EXPECT_EQ(mseq.has_key_tag(), mseq.get()->m_type.has_key_tag());
+    EXPECT_EQ(mval.has_key_tag(), mval.get()->m_type.has_key_tag());
+    EXPECT_EQ(mvalnotag.has_key_tag(), mvalnotag.get()->m_type.has_key_tag());
+    EXPECT_EQ(mdocval.has_key_tag(), mdocval.get()->m_type.has_key_tag());
 }
 
 TEST(NodeType, has_val_tag)
@@ -1653,15 +1839,24 @@ a scalar
     const size_t val_id = t.first_child(seq_id);
     const size_t valnotag_id = t.last_child(seq_id);
     const size_t docval_id = t.last_child(stream_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnotag = t.ref(keyvalnotag_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnotag = t.ref(valnotag_id);
-    const NodeRef docval = t.ref(docval_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnotag = t.ref(keyvalnotag_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnotag = t.ref(valnotag_id);
+    ConstNodeRef docval = t.ref(docval_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnotag = t.ref(keyvalnotag_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnotag = t.ref(valnotag_id);
+    NodeRef mdocval = t.ref(docval_id);
     EXPECT_FALSE(t.has_val_tag(stream_id));
     EXPECT_TRUE(t.has_val_tag(doc_id));
     EXPECT_TRUE(t.has_val_tag(map_id));
@@ -1680,6 +1875,15 @@ a scalar
     EXPECT_TRUE(val.has_val_tag());
     EXPECT_FALSE(valnotag.has_val_tag());
     EXPECT_FALSE(docval.has_val_tag());
+    EXPECT_FALSE(mstream.has_val_tag());
+    EXPECT_TRUE(mdoc.has_val_tag());
+    EXPECT_TRUE(mmap.has_val_tag());
+    EXPECT_TRUE(mkeyval.has_val_tag());
+    EXPECT_FALSE(mkeyvalnotag.has_val_tag());
+    EXPECT_TRUE(mseq.has_val_tag());
+    EXPECT_TRUE(mval.has_val_tag());
+    EXPECT_FALSE(mvalnotag.has_val_tag());
+    EXPECT_FALSE(mdocval.has_val_tag());
     EXPECT_EQ(t.has_val_tag(stream_id), t._p(stream_id)->m_type.has_val_tag());
     EXPECT_EQ(t.has_val_tag(doc_id), t._p(doc_id)->m_type.has_val_tag());
     EXPECT_EQ(t.has_val_tag(map_id), t._p(map_id)->m_type.has_val_tag());
@@ -1698,6 +1902,15 @@ a scalar
     EXPECT_EQ(val.has_val_tag(), val.get()->m_type.has_val_tag());
     EXPECT_EQ(valnotag.has_val_tag(), valnotag.get()->m_type.has_val_tag());
     EXPECT_EQ(docval.has_val_tag(), docval.get()->m_type.has_val_tag());
+    EXPECT_EQ(mstream.has_val_tag(), mstream.get()->m_type.has_val_tag());
+    EXPECT_EQ(mdoc.has_val_tag(), mdoc.get()->m_type.has_val_tag());
+    EXPECT_EQ(mmap.has_val_tag(), mmap.get()->m_type.has_val_tag());
+    EXPECT_EQ(mkeyval.has_val_tag(), mkeyval.get()->m_type.has_val_tag());
+    EXPECT_EQ(mkeyvalnotag.has_val_tag(), mkeyvalnotag.get()->m_type.has_val_tag());
+    EXPECT_EQ(mseq.has_val_tag(), mseq.get()->m_type.has_val_tag());
+    EXPECT_EQ(mval.has_val_tag(), mval.get()->m_type.has_val_tag());
+    EXPECT_EQ(mvalnotag.has_val_tag(), mvalnotag.get()->m_type.has_val_tag());
+    EXPECT_EQ(mdocval.has_val_tag(), mdocval.get()->m_type.has_val_tag());
 }
 
 TEST(NodeType, has_key_anchor)
@@ -1721,14 +1934,22 @@ TEST(Tree, has_key_anchor)
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t valnoanchor_id = t.last_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnoanchor = t.ref(valnoanchor_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnoanchor = t.ref(valnoanchor_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnoanchor = t.ref(keyvalnoanchor_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnoanchor = t.ref(valnoanchor_id);
     EXPECT_FALSE(t.has_key_anchor(stream_id));
     EXPECT_FALSE(t.has_key_anchor(doc_id));
     EXPECT_TRUE(t.has_key_anchor(map_id));
@@ -1745,6 +1966,14 @@ TEST(Tree, has_key_anchor)
     EXPECT_TRUE(seq.has_key_anchor());
     EXPECT_FALSE(val.has_key_anchor());
     EXPECT_FALSE(valnoanchor.has_key_anchor());
+    EXPECT_FALSE(mstream.has_key_anchor());
+    EXPECT_FALSE(mdoc.has_key_anchor());
+    EXPECT_TRUE(mmap.has_key_anchor());
+    EXPECT_TRUE(mkeyval.has_key_anchor());
+    EXPECT_FALSE(mkeyvalnoanchor.has_key_anchor());
+    EXPECT_TRUE(mseq.has_key_anchor());
+    EXPECT_FALSE(mval.has_key_anchor());
+    EXPECT_FALSE(mvalnoanchor.has_key_anchor());
     EXPECT_EQ(t.has_key_anchor(stream_id), t._p(stream_id)->m_type.has_key_anchor());
     EXPECT_EQ(t.has_key_anchor(doc_id), t._p(doc_id)->m_type.has_key_anchor());
     EXPECT_EQ(t.has_key_anchor(map_id), t._p(map_id)->m_type.has_key_anchor());
@@ -1761,6 +1990,14 @@ TEST(Tree, has_key_anchor)
     EXPECT_EQ(seq.has_key_anchor(), seq.get()->m_type.has_key_anchor());
     EXPECT_EQ(val.has_key_anchor(), val.get()->m_type.has_key_anchor());
     EXPECT_EQ(valnoanchor.has_key_anchor(), valnoanchor.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mstream.has_key_anchor(), mstream.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mdoc.has_key_anchor(), mdoc.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mmap.has_key_anchor(), mmap.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mkeyval.has_key_anchor(), mkeyval.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mkeyvalnoanchor.has_key_anchor(), mkeyvalnoanchor.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mseq.has_key_anchor(), mseq.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mval.has_key_anchor(), mval.get()->m_type.has_key_anchor());
+    EXPECT_EQ(mvalnoanchor.has_key_anchor(), mvalnoanchor.get()->m_type.has_key_anchor());
 }
 
 TEST(NodeType, is_key_anchor)
@@ -1784,14 +2021,22 @@ TEST(Tree, is_key_anchor)
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t valnoanchor_id = t.last_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnoanchor = t.ref(valnoanchor_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnoanchor = t.ref(valnoanchor_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnoanchor = t.ref(keyvalnoanchor_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnoanchor = t.ref(valnoanchor_id);
     EXPECT_FALSE(t.is_key_anchor(stream_id));
     EXPECT_FALSE(t.is_key_anchor(doc_id));
     EXPECT_TRUE(t.is_key_anchor(map_id));
@@ -1808,6 +2053,14 @@ TEST(Tree, is_key_anchor)
     EXPECT_TRUE(seq.is_key_anchor());
     EXPECT_FALSE(val.is_key_anchor());
     EXPECT_FALSE(valnoanchor.is_key_anchor());
+    EXPECT_FALSE(mstream.is_key_anchor());
+    EXPECT_FALSE(mdoc.is_key_anchor());
+    EXPECT_TRUE(mmap.is_key_anchor());
+    EXPECT_TRUE(mkeyval.is_key_anchor());
+    EXPECT_FALSE(mkeyvalnoanchor.is_key_anchor());
+    EXPECT_TRUE(mseq.is_key_anchor());
+    EXPECT_FALSE(mval.is_key_anchor());
+    EXPECT_FALSE(mvalnoanchor.is_key_anchor());
     EXPECT_EQ(t.is_key_anchor(stream_id), t._p(stream_id)->m_type.is_key_anchor());
     EXPECT_EQ(t.is_key_anchor(doc_id), t._p(doc_id)->m_type.is_key_anchor());
     EXPECT_EQ(t.is_key_anchor(map_id), t._p(map_id)->m_type.is_key_anchor());
@@ -1824,6 +2077,14 @@ TEST(Tree, is_key_anchor)
     EXPECT_EQ(seq.is_key_anchor(), seq.get()->m_type.is_key_anchor());
     EXPECT_EQ(val.is_key_anchor(), val.get()->m_type.is_key_anchor());
     EXPECT_EQ(valnoanchor.is_key_anchor(), valnoanchor.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mstream.is_key_anchor(), mstream.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mdoc.is_key_anchor(), mdoc.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mmap.is_key_anchor(), mmap.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mkeyval.is_key_anchor(), mkeyval.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mkeyvalnoanchor.is_key_anchor(), mkeyvalnoanchor.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mseq.is_key_anchor(), mseq.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mval.is_key_anchor(), mval.get()->m_type.is_key_anchor());
+    EXPECT_EQ(mvalnoanchor.is_key_anchor(), mvalnoanchor.get()->m_type.is_key_anchor());
 }
 
 TEST(NodeType, has_val_anchor)
@@ -1847,14 +2108,22 @@ seq: &seqanchor [&valanchor foo, bar]
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t valnoanchor_id = t.last_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnoanchor = t.ref(valnoanchor_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnoanchor = t.ref(valnoanchor_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnoanchor = t.ref(keyvalnoanchor_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnoanchor = t.ref(valnoanchor_id);
     EXPECT_FALSE(t.has_val_anchor(stream_id));
     EXPECT_FALSE(t.has_val_anchor(doc_id));
     EXPECT_TRUE(t.has_val_anchor(map_id));
@@ -1871,6 +2140,14 @@ seq: &seqanchor [&valanchor foo, bar]
     EXPECT_TRUE(seq.has_val_anchor());
     EXPECT_TRUE(val.has_val_anchor());
     EXPECT_FALSE(valnoanchor.has_val_anchor());
+    EXPECT_FALSE(mstream.has_val_anchor());
+    EXPECT_FALSE(mdoc.has_val_anchor());
+    EXPECT_TRUE(mmap.has_val_anchor());
+    EXPECT_TRUE(mkeyval.has_val_anchor());
+    EXPECT_FALSE(mkeyvalnoanchor.has_val_anchor());
+    EXPECT_TRUE(mseq.has_val_anchor());
+    EXPECT_TRUE(mval.has_val_anchor());
+    EXPECT_FALSE(mvalnoanchor.has_val_anchor());
     EXPECT_EQ(t.has_val_anchor(stream_id), t._p(stream_id)->m_type.has_val_anchor());
     EXPECT_EQ(t.has_val_anchor(doc_id), t._p(doc_id)->m_type.has_val_anchor());
     EXPECT_EQ(t.has_val_anchor(map_id), t._p(map_id)->m_type.has_val_anchor());
@@ -1887,6 +2164,14 @@ seq: &seqanchor [&valanchor foo, bar]
     EXPECT_EQ(seq.has_val_anchor(), seq.get()->m_type.has_val_anchor());
     EXPECT_EQ(val.has_val_anchor(), val.get()->m_type.has_val_anchor());
     EXPECT_EQ(valnoanchor.has_val_anchor(), valnoanchor.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mstream.has_val_anchor(), mstream.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mdoc.has_val_anchor(), mdoc.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mmap.has_val_anchor(), mmap.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mkeyval.has_val_anchor(), mkeyval.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mkeyvalnoanchor.has_val_anchor(), mkeyvalnoanchor.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mseq.has_val_anchor(), mseq.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mval.has_val_anchor(), mval.get()->m_type.has_val_anchor());
+    EXPECT_EQ(mvalnoanchor.has_val_anchor(), mvalnoanchor.get()->m_type.has_val_anchor());
 }
 
 TEST(NodeType, is_val_anchor)
@@ -1910,14 +2195,22 @@ seq: &seqanchor [&valanchor foo, bar]
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t valnoanchor_id = t.last_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnoanchor = t.ref(valnoanchor_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnoanchor = t.ref(valnoanchor_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnoanchor = t.ref(keyvalnoanchor_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnoanchor = t.ref(valnoanchor_id);
     EXPECT_FALSE(t.is_val_anchor(stream_id));
     EXPECT_FALSE(t.is_val_anchor(doc_id));
     EXPECT_TRUE(t.is_val_anchor(map_id));
@@ -1934,6 +2227,14 @@ seq: &seqanchor [&valanchor foo, bar]
     EXPECT_TRUE(seq.is_val_anchor());
     EXPECT_TRUE(val.is_val_anchor());
     EXPECT_FALSE(valnoanchor.is_val_anchor());
+    EXPECT_FALSE(mstream.is_val_anchor());
+    EXPECT_FALSE(mdoc.is_val_anchor());
+    EXPECT_TRUE(mmap.is_val_anchor());
+    EXPECT_TRUE(mkeyval.is_val_anchor());
+    EXPECT_FALSE(mkeyvalnoanchor.is_val_anchor());
+    EXPECT_TRUE(mseq.is_val_anchor());
+    EXPECT_TRUE(mval.is_val_anchor());
+    EXPECT_FALSE(mvalnoanchor.is_val_anchor());
     EXPECT_EQ(t.is_val_anchor(stream_id), t._p(stream_id)->m_type.is_val_anchor());
     EXPECT_EQ(t.is_val_anchor(doc_id), t._p(doc_id)->m_type.is_val_anchor());
     EXPECT_EQ(t.is_val_anchor(map_id), t._p(map_id)->m_type.is_val_anchor());
@@ -1950,6 +2251,14 @@ seq: &seqanchor [&valanchor foo, bar]
     EXPECT_EQ(seq.is_val_anchor(), seq.get()->m_type.is_val_anchor());
     EXPECT_EQ(val.is_val_anchor(), val.get()->m_type.is_val_anchor());
     EXPECT_EQ(valnoanchor.is_val_anchor(), valnoanchor.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mstream.is_val_anchor(), mstream.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mdoc.is_val_anchor(), mdoc.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mmap.is_val_anchor(), mmap.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mkeyval.is_val_anchor(), mkeyval.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mkeyvalnoanchor.is_val_anchor(), mkeyvalnoanchor.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mseq.is_val_anchor(), mseq.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mval.is_val_anchor(), mval.get()->m_type.is_val_anchor());
+    EXPECT_EQ(mvalnoanchor.is_val_anchor(), mvalnoanchor.get()->m_type.is_val_anchor());
 }
 
 TEST(NodeType, has_anchor)
@@ -1978,14 +2287,22 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none}
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t valnoanchor_id = t.last_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnoanchor = t.ref(valnoanchor_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnoanchor = t.ref(valnoanchor_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnoanchor = t.ref(keyvalnoanchor_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnoanchor = t.ref(valnoanchor_id);
     EXPECT_FALSE(t.has_anchor(stream_id));
     EXPECT_FALSE(t.has_anchor(doc_id));
     EXPECT_TRUE(t.has_anchor(map_id));
@@ -2002,6 +2319,14 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none}
     EXPECT_TRUE(seq.has_anchor());
     EXPECT_TRUE(val.has_anchor());
     EXPECT_FALSE(valnoanchor.has_anchor());
+    EXPECT_FALSE(mstream.has_anchor());
+    EXPECT_FALSE(mdoc.has_anchor());
+    EXPECT_TRUE(mmap.has_anchor());
+    EXPECT_TRUE(mkeyval.has_anchor());
+    EXPECT_FALSE(mkeyvalnoanchor.has_anchor());
+    EXPECT_TRUE(mseq.has_anchor());
+    EXPECT_TRUE(mval.has_anchor());
+    EXPECT_FALSE(mvalnoanchor.has_anchor());
     EXPECT_EQ(t.has_anchor(stream_id), t._p(stream_id)->m_type.has_anchor());
     EXPECT_EQ(t.has_anchor(doc_id), t._p(doc_id)->m_type.has_anchor());
     EXPECT_EQ(t.has_anchor(map_id), t._p(map_id)->m_type.has_anchor());
@@ -2018,6 +2343,14 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none}
     EXPECT_EQ(seq.has_anchor(), seq.get()->m_type.has_anchor());
     EXPECT_EQ(val.has_anchor(), val.get()->m_type.has_anchor());
     EXPECT_EQ(valnoanchor.has_anchor(), valnoanchor.get()->m_type.has_anchor());
+    EXPECT_EQ(mstream.has_anchor(), mstream.get()->m_type.has_anchor());
+    EXPECT_EQ(mdoc.has_anchor(), mdoc.get()->m_type.has_anchor());
+    EXPECT_EQ(mmap.has_anchor(), mmap.get()->m_type.has_anchor());
+    EXPECT_EQ(mkeyval.has_anchor(), mkeyval.get()->m_type.has_anchor());
+    EXPECT_EQ(mkeyvalnoanchor.has_anchor(), mkeyvalnoanchor.get()->m_type.has_anchor());
+    EXPECT_EQ(mseq.has_anchor(), mseq.get()->m_type.has_anchor());
+    EXPECT_EQ(mval.has_anchor(), mval.get()->m_type.has_anchor());
+    EXPECT_EQ(mvalnoanchor.has_anchor(), mvalnoanchor.get()->m_type.has_anchor());
 }
 
 TEST(NodeType, is_anchor)
@@ -2046,14 +2379,22 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none}
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
     const size_t valnoanchor_id = t.last_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
-    const NodeRef valnoanchor = t.ref(valnoanchor_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef keyvalnoanchor = t.ref(keyvalnoanchor_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    ConstNodeRef valnoanchor = t.ref(valnoanchor_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mkeyvalnoanchor = t.ref(keyvalnoanchor_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
+    NodeRef mvalnoanchor = t.ref(valnoanchor_id);
     EXPECT_FALSE(t.is_anchor(stream_id));
     EXPECT_FALSE(t.is_anchor(doc_id));
     EXPECT_TRUE(t.is_anchor(map_id));
@@ -2070,6 +2411,14 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none}
     EXPECT_TRUE(seq.is_anchor());
     EXPECT_TRUE(val.is_anchor());
     EXPECT_FALSE(valnoanchor.is_anchor());
+    EXPECT_FALSE(mstream.is_anchor());
+    EXPECT_FALSE(mdoc.is_anchor());
+    EXPECT_TRUE(mmap.is_anchor());
+    EXPECT_TRUE(mkeyval.is_anchor());
+    EXPECT_FALSE(mkeyvalnoanchor.is_anchor());
+    EXPECT_TRUE(mseq.is_anchor());
+    EXPECT_TRUE(mval.is_anchor());
+    EXPECT_FALSE(mvalnoanchor.is_anchor());
     EXPECT_EQ(t.is_anchor(stream_id), t._p(stream_id)->m_type.is_anchor());
     EXPECT_EQ(t.is_anchor(doc_id), t._p(doc_id)->m_type.is_anchor());
     EXPECT_EQ(t.is_anchor(map_id), t._p(map_id)->m_type.is_anchor());
@@ -2086,6 +2435,14 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none}
     EXPECT_EQ(seq.is_anchor(), seq.get()->m_type.is_anchor());
     EXPECT_EQ(val.is_anchor(), val.get()->m_type.is_anchor());
     EXPECT_EQ(valnoanchor.is_anchor(), valnoanchor.get()->m_type.is_anchor());
+    EXPECT_EQ(mstream.is_anchor(), mstream.get()->m_type.is_anchor());
+    EXPECT_EQ(mdoc.is_anchor(), mdoc.get()->m_type.is_anchor());
+    EXPECT_EQ(mmap.is_anchor(), mmap.get()->m_type.is_anchor());
+    EXPECT_EQ(mkeyval.is_anchor(), mkeyval.get()->m_type.is_anchor());
+    EXPECT_EQ(mkeyvalnoanchor.is_anchor(), mkeyvalnoanchor.get()->m_type.is_anchor());
+    EXPECT_EQ(mseq.is_anchor(), mseq.get()->m_type.is_anchor());
+    EXPECT_EQ(mval.is_anchor(), mval.get()->m_type.is_anchor());
+    EXPECT_EQ(mvalnoanchor.is_anchor(), mvalnoanchor.get()->m_type.is_anchor());
 }
 
 TEST(NodeType, is_key_ref)
@@ -2107,12 +2464,18 @@ TEST(Tree, is_key_ref)
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_FALSE(t.is_key_ref(stream_id));
     EXPECT_FALSE(t.is_key_ref(doc_id));
     EXPECT_TRUE(t.is_key_ref(map_id));
@@ -2125,6 +2488,12 @@ TEST(Tree, is_key_ref)
     EXPECT_FALSE(keyval.is_key_ref());
     EXPECT_TRUE(seq.is_key_ref());
     EXPECT_FALSE(val.is_key_ref());
+    EXPECT_FALSE(mstream.is_key_ref());
+    EXPECT_FALSE(mdoc.is_key_ref());
+    EXPECT_TRUE(mmap.is_key_ref());
+    EXPECT_FALSE(mkeyval.is_key_ref());
+    EXPECT_TRUE(mseq.is_key_ref());
+    EXPECT_FALSE(mval.is_key_ref());
     EXPECT_EQ(t.is_key_ref(stream_id), t._p(stream_id)->m_type.is_key_ref());
     EXPECT_EQ(t.is_key_ref(doc_id), t._p(doc_id)->m_type.is_key_ref());
     EXPECT_EQ(t.is_key_ref(map_id), t._p(map_id)->m_type.is_key_ref());
@@ -2137,6 +2506,12 @@ TEST(Tree, is_key_ref)
     EXPECT_EQ(keyval.is_key_ref(), keyval.get()->m_type.is_key_ref());
     EXPECT_EQ(seq.is_key_ref(), seq.get()->m_type.is_key_ref());
     EXPECT_EQ(val.is_key_ref(), val.get()->m_type.is_key_ref());
+    EXPECT_EQ(mstream.is_key_ref(), mstream.get()->m_type.is_key_ref());
+    EXPECT_EQ(mdoc.is_key_ref(), mdoc.get()->m_type.is_key_ref());
+    EXPECT_EQ(mmap.is_key_ref(), mmap.get()->m_type.is_key_ref());
+    EXPECT_EQ(mkeyval.is_key_ref(), mkeyval.get()->m_type.is_key_ref());
+    EXPECT_EQ(mseq.is_key_ref(), mseq.get()->m_type.is_key_ref());
+    EXPECT_EQ(mval.is_key_ref(), mval.get()->m_type.is_key_ref());
 }
 
 TEST(NodeType, is_val_ref)
@@ -2158,12 +2533,18 @@ seq: [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_FALSE(t.is_val_ref(stream_id));
     EXPECT_FALSE(t.is_val_ref(doc_id));
     EXPECT_FALSE(t.is_val_ref(map_id));
@@ -2176,6 +2557,12 @@ seq: [*valref, bar]
     EXPECT_TRUE(keyval.is_val_ref());
     EXPECT_FALSE(seq.is_val_ref());
     EXPECT_TRUE(val.is_val_ref());
+    EXPECT_FALSE(mstream.is_val_ref());
+    EXPECT_FALSE(mdoc.is_val_ref());
+    EXPECT_FALSE(mmap.is_val_ref());
+    EXPECT_TRUE(mkeyval.is_val_ref());
+    EXPECT_FALSE(mseq.is_val_ref());
+    EXPECT_TRUE(mval.is_val_ref());
     EXPECT_EQ(t.is_val_ref(stream_id), t._p(stream_id)->m_type.is_val_ref());
     EXPECT_EQ(t.is_val_ref(doc_id), t._p(doc_id)->m_type.is_val_ref());
     EXPECT_EQ(t.is_val_ref(map_id), t._p(map_id)->m_type.is_val_ref());
@@ -2188,6 +2575,12 @@ seq: [*valref, bar]
     EXPECT_EQ(keyval.is_val_ref(), keyval.get()->m_type.is_val_ref());
     EXPECT_EQ(seq.is_val_ref(), seq.get()->m_type.is_val_ref());
     EXPECT_EQ(val.is_val_ref(), val.get()->m_type.is_val_ref());
+    EXPECT_EQ(mstream.is_val_ref(), mstream.get()->m_type.is_val_ref());
+    EXPECT_EQ(mdoc.is_val_ref(), mdoc.get()->m_type.is_val_ref());
+    EXPECT_EQ(mmap.is_val_ref(), mmap.get()->m_type.is_val_ref());
+    EXPECT_EQ(mkeyval.is_val_ref(), mkeyval.get()->m_type.is_val_ref());
+    EXPECT_EQ(mseq.is_val_ref(), mseq.get()->m_type.is_val_ref());
+    EXPECT_EQ(mval.is_val_ref(), mval.get()->m_type.is_val_ref());
 }
 
 TEST(NodeType, is_ref)
@@ -2213,12 +2606,18 @@ seq: [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_FALSE(t.is_ref(stream_id));
     EXPECT_FALSE(t.is_ref(doc_id));
     EXPECT_FALSE(t.is_ref(map_id));
@@ -2231,6 +2630,12 @@ seq: [*valref, bar]
     EXPECT_TRUE(keyval.is_ref());
     EXPECT_FALSE(seq.is_ref());
     EXPECT_TRUE(val.is_ref());
+    EXPECT_FALSE(mstream.is_ref());
+    EXPECT_FALSE(mdoc.is_ref());
+    EXPECT_FALSE(mmap.is_ref());
+    EXPECT_TRUE(mkeyval.is_ref());
+    EXPECT_FALSE(mseq.is_ref());
+    EXPECT_TRUE(mval.is_ref());
     EXPECT_EQ(t.is_ref(stream_id), t._p(stream_id)->m_type.is_ref());
     EXPECT_EQ(t.is_ref(doc_id), t._p(doc_id)->m_type.is_ref());
     EXPECT_EQ(t.is_ref(map_id), t._p(map_id)->m_type.is_ref());
@@ -2243,6 +2648,12 @@ seq: [*valref, bar]
     EXPECT_EQ(keyval.is_ref(), keyval.get()->m_type.is_ref());
     EXPECT_EQ(seq.is_ref(), seq.get()->m_type.is_ref());
     EXPECT_EQ(val.is_ref(), val.get()->m_type.is_ref());
+    EXPECT_EQ(mstream.is_ref(), mstream.get()->m_type.is_ref());
+    EXPECT_EQ(mdoc.is_ref(), mdoc.get()->m_type.is_ref());
+    EXPECT_EQ(mmap.is_ref(), mmap.get()->m_type.is_ref());
+    EXPECT_EQ(mkeyval.is_ref(), mkeyval.get()->m_type.is_ref());
+    EXPECT_EQ(mseq.is_ref(), mseq.get()->m_type.is_ref());
+    EXPECT_EQ(mval.is_ref(), mval.get()->m_type.is_ref());
 }
 
 TEST(NodeType, is_anchor_or_ref)
@@ -2274,12 +2685,18 @@ seq: &seq [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_FALSE(t.is_anchor_or_ref(stream_id));
     EXPECT_FALSE(t.is_anchor_or_ref(doc_id));
     EXPECT_TRUE(t.is_anchor_or_ref(map_id));
@@ -2292,6 +2709,12 @@ seq: &seq [*valref, bar]
     EXPECT_TRUE(keyval.is_anchor_or_ref());
     EXPECT_TRUE(seq.is_anchor_or_ref());
     EXPECT_TRUE(val.is_anchor_or_ref());
+    EXPECT_FALSE(mstream.is_anchor_or_ref());
+    EXPECT_FALSE(mdoc.is_anchor_or_ref());
+    EXPECT_TRUE(mmap.is_anchor_or_ref());
+    EXPECT_TRUE(mkeyval.is_anchor_or_ref());
+    EXPECT_TRUE(mseq.is_anchor_or_ref());
+    EXPECT_TRUE(mval.is_anchor_or_ref());
     EXPECT_EQ(t.is_anchor_or_ref(stream_id), t._p(stream_id)->m_type.is_anchor_or_ref());
     EXPECT_EQ(t.is_anchor_or_ref(doc_id), t._p(doc_id)->m_type.is_anchor_or_ref());
     EXPECT_EQ(t.is_anchor_or_ref(map_id), t._p(map_id)->m_type.is_anchor_or_ref());
@@ -2304,6 +2727,12 @@ seq: &seq [*valref, bar]
     EXPECT_EQ(keyval.is_anchor_or_ref(), keyval.get()->m_type.is_anchor_or_ref());
     EXPECT_EQ(seq.is_anchor_or_ref(), seq.get()->m_type.is_anchor_or_ref());
     EXPECT_EQ(val.is_anchor_or_ref(), val.get()->m_type.is_anchor_or_ref());
+    EXPECT_EQ(mstream.is_anchor_or_ref(), mstream.get()->m_type.is_anchor_or_ref());
+    EXPECT_EQ(mdoc.is_anchor_or_ref(), mdoc.get()->m_type.is_anchor_or_ref());
+    EXPECT_EQ(mmap.is_anchor_or_ref(), mmap.get()->m_type.is_anchor_or_ref());
+    EXPECT_EQ(mkeyval.is_anchor_or_ref(), mkeyval.get()->m_type.is_anchor_or_ref());
+    EXPECT_EQ(mseq.is_anchor_or_ref(), mseq.get()->m_type.is_anchor_or_ref());
+    EXPECT_EQ(mval.is_anchor_or_ref(), mval.get()->m_type.is_anchor_or_ref());
 }
 
 TEST(NodeType, is_key_quoted)
@@ -2322,21 +2751,30 @@ notquoted: bar
     const size_t map_id = t.first_child(t.root_id());
     const size_t quoted_id = t.first_child(map_id);
     const size_t notquoted_id = t.last_child(map_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef quoted = t.ref(quoted_id);
-    const NodeRef notquoted = t.ref(notquoted_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef quoted = t.ref(quoted_id);
+    ConstNodeRef notquoted = t.ref(notquoted_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mquoted = t.ref(quoted_id);
+    NodeRef mnotquoted = t.ref(notquoted_id);
     EXPECT_FALSE(t.is_key_quoted(map_id));
     EXPECT_TRUE(t.is_key_quoted(quoted_id));
     EXPECT_FALSE(t.is_key_quoted(notquoted_id));
     EXPECT_FALSE(map.is_key_quoted());
     EXPECT_TRUE(quoted.is_key_quoted());
     EXPECT_FALSE(notquoted.is_key_quoted());
+    EXPECT_FALSE(mmap.is_key_quoted());
+    EXPECT_TRUE(mquoted.is_key_quoted());
+    EXPECT_FALSE(mnotquoted.is_key_quoted());
     EXPECT_EQ(t.is_key_quoted(map_id), t._p(map_id)->m_type.is_key_quoted());
     EXPECT_EQ(t.is_key_quoted(quoted_id), t._p(quoted_id)->m_type.is_key_quoted());
     EXPECT_EQ(t.is_key_quoted(notquoted_id), t._p(notquoted_id)->m_type.is_key_quoted());
     EXPECT_EQ(map.is_key_quoted(), map.get()->m_type.is_key_quoted());
     EXPECT_EQ(quoted.is_key_quoted(), quoted.get()->m_type.is_key_quoted());
     EXPECT_EQ(notquoted.is_key_quoted(), notquoted.get()->m_type.is_key_quoted());
+    EXPECT_EQ(mmap.is_key_quoted(), mmap.get()->m_type.is_key_quoted());
+    EXPECT_EQ(mquoted.is_key_quoted(), mquoted.get()->m_type.is_key_quoted());
+    EXPECT_EQ(mnotquoted.is_key_quoted(), mnotquoted.get()->m_type.is_key_quoted());
 }
 
 TEST(NodeType, is_val_quoted)
@@ -2355,21 +2793,30 @@ notquoted: bar
     const size_t map_id = t.first_child(t.root_id());
     const size_t quoted_id = t.first_child(map_id);
     const size_t notquoted_id = t.last_child(map_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef quoted = t.ref(quoted_id);
-    const NodeRef notquoted = t.ref(notquoted_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef quoted = t.ref(quoted_id);
+    ConstNodeRef notquoted = t.ref(notquoted_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mquoted = t.ref(quoted_id);
+    NodeRef mnotquoted = t.ref(notquoted_id);
     EXPECT_FALSE(t.is_val_quoted(map_id));
     EXPECT_TRUE(t.is_val_quoted(quoted_id));
     EXPECT_FALSE(t.is_val_quoted(notquoted_id));
     EXPECT_FALSE(map.is_val_quoted());
     EXPECT_TRUE(quoted.is_val_quoted());
     EXPECT_FALSE(notquoted.is_val_quoted());
+    EXPECT_FALSE(mmap.is_val_quoted());
+    EXPECT_TRUE(mquoted.is_val_quoted());
+    EXPECT_FALSE(mnotquoted.is_val_quoted());
     EXPECT_EQ(t.is_val_quoted(map_id), t._p(map_id)->m_type.is_val_quoted());
     EXPECT_EQ(t.is_val_quoted(quoted_id), t._p(quoted_id)->m_type.is_val_quoted());
     EXPECT_EQ(t.is_val_quoted(notquoted_id), t._p(notquoted_id)->m_type.is_val_quoted());
     EXPECT_EQ(map.is_val_quoted(), map.get()->m_type.is_val_quoted());
     EXPECT_EQ(quoted.is_val_quoted(), quoted.get()->m_type.is_val_quoted());
     EXPECT_EQ(notquoted.is_val_quoted(), notquoted.get()->m_type.is_val_quoted());
+    EXPECT_EQ(mmap.is_val_quoted(), mmap.get()->m_type.is_val_quoted());
+    EXPECT_EQ(mquoted.is_val_quoted(), mquoted.get()->m_type.is_val_quoted());
+    EXPECT_EQ(mnotquoted.is_val_quoted(), mnotquoted.get()->m_type.is_val_quoted());
 }
 
 TEST(NodeType, is_quoted)
@@ -2403,14 +2850,22 @@ notquoted: bar
     const size_t quoted5_id = t.find_child(map_id, "quoted5");
     const size_t quoted6_id = t.find_child(map_id, "quoted6");
     const size_t notquoted_id = t.last_child(map_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef quoted1 = t.ref(quoted1_id);
-    const NodeRef quoted2 = t.ref(quoted2_id);
-    const NodeRef quoted3 = t.ref(quoted3_id);
-    const NodeRef quoted4 = t.ref(quoted4_id);
-    const NodeRef quoted5 = t.ref(quoted5_id);
-    const NodeRef quoted6 = t.ref(quoted6_id);
-    const NodeRef notquoted = t.ref(notquoted_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef quoted1 = t.ref(quoted1_id);
+    ConstNodeRef quoted2 = t.ref(quoted2_id);
+    ConstNodeRef quoted3 = t.ref(quoted3_id);
+    ConstNodeRef quoted4 = t.ref(quoted4_id);
+    ConstNodeRef quoted5 = t.ref(quoted5_id);
+    ConstNodeRef quoted6 = t.ref(quoted6_id);
+    ConstNodeRef notquoted = t.ref(notquoted_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mquoted1 = t.ref(quoted1_id);
+    NodeRef mquoted2 = t.ref(quoted2_id);
+    NodeRef mquoted3 = t.ref(quoted3_id);
+    NodeRef mquoted4 = t.ref(quoted4_id);
+    NodeRef mquoted5 = t.ref(quoted5_id);
+    NodeRef mquoted6 = t.ref(quoted6_id);
+    NodeRef mnotquoted = t.ref(notquoted_id);
     EXPECT_FALSE(t.is_quoted(map_id));
     EXPECT_TRUE(t.is_quoted(quoted1_id));
     EXPECT_TRUE(t.is_quoted(quoted2_id));
@@ -2427,6 +2882,14 @@ notquoted: bar
     EXPECT_TRUE(quoted5.is_quoted());
     EXPECT_TRUE(quoted6.is_quoted());
     EXPECT_FALSE(notquoted.is_quoted());
+    EXPECT_FALSE(mmap.is_quoted());
+    EXPECT_TRUE(mquoted1.is_quoted());
+    EXPECT_TRUE(mquoted2.is_quoted());
+    EXPECT_TRUE(mquoted3.is_quoted());
+    EXPECT_TRUE(mquoted4.is_quoted());
+    EXPECT_TRUE(mquoted5.is_quoted());
+    EXPECT_TRUE(mquoted6.is_quoted());
+    EXPECT_FALSE(mnotquoted.is_quoted());
     EXPECT_EQ(t.is_quoted(map_id), t._p(map_id)->m_type.is_quoted());
     EXPECT_EQ(t.is_quoted(quoted1_id), t._p(quoted1_id)->m_type.is_quoted());
     EXPECT_EQ(t.is_quoted(quoted2_id), t._p(quoted2_id)->m_type.is_quoted());
@@ -2443,6 +2906,14 @@ notquoted: bar
     EXPECT_EQ(quoted5.is_quoted(), quoted5.get()->m_type.is_quoted());
     EXPECT_EQ(quoted6.is_quoted(), quoted6.get()->m_type.is_quoted());
     EXPECT_EQ(notquoted.is_quoted(), notquoted.get()->m_type.is_quoted());
+    EXPECT_EQ(mmap.is_quoted(), mmap.get()->m_type.is_quoted());
+    EXPECT_EQ(mquoted1.is_quoted(), mquoted1.get()->m_type.is_quoted());
+    EXPECT_EQ(mquoted2.is_quoted(), mquoted2.get()->m_type.is_quoted());
+    EXPECT_EQ(mquoted3.is_quoted(), mquoted3.get()->m_type.is_quoted());
+    EXPECT_EQ(mquoted4.is_quoted(), mquoted4.get()->m_type.is_quoted());
+    EXPECT_EQ(mquoted5.is_quoted(), mquoted5.get()->m_type.is_quoted());
+    EXPECT_EQ(mquoted6.is_quoted(), mquoted6.get()->m_type.is_quoted());
+    EXPECT_EQ(mnotquoted.is_quoted(), mnotquoted.get()->m_type.is_quoted());
 }
 
 
@@ -2458,11 +2929,16 @@ seq: &seq [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     //EXPECT_FALSE(t.parent_is_seq(stream_id));
     EXPECT_TRUE(t.parent_is_seq(doc_id));
     EXPECT_FALSE(t.parent_is_seq(map_id));
@@ -2475,12 +2951,23 @@ seq: &seq [*valref, bar]
     EXPECT_FALSE(keyval.parent_is_seq());
     EXPECT_FALSE(seq.parent_is_seq());
     EXPECT_TRUE(val.parent_is_seq());
+    //EXPECT_FALSE(mstream.parent_is_seq());
+    EXPECT_TRUE(mdoc.parent_is_seq());
+    EXPECT_FALSE(mmap.parent_is_seq());
+    EXPECT_FALSE(mkeyval.parent_is_seq());
+    EXPECT_FALSE(mseq.parent_is_seq());
+    EXPECT_TRUE(mval.parent_is_seq());
     //EXPECT_EQ(t.parent_is_seq(stream_id), stream.parent_is_seq());
     EXPECT_EQ(t.parent_is_seq(doc_id), doc.parent_is_seq());
     EXPECT_EQ(t.parent_is_seq(map_id), map.parent_is_seq());
     EXPECT_EQ(t.parent_is_seq(keyval_id), keyval.parent_is_seq());
     EXPECT_EQ(t.parent_is_seq(seq_id), seq.parent_is_seq());
     EXPECT_EQ(t.parent_is_seq(val_id), val.parent_is_seq());
+    EXPECT_EQ(t.parent_is_seq(doc_id), mdoc.parent_is_seq());
+    EXPECT_EQ(t.parent_is_seq(map_id), mmap.parent_is_seq());
+    EXPECT_EQ(t.parent_is_seq(keyval_id), mkeyval.parent_is_seq());
+    EXPECT_EQ(t.parent_is_seq(seq_id), mseq.parent_is_seq());
+    EXPECT_EQ(t.parent_is_seq(val_id), mval.parent_is_seq());
 }
 
 TEST(Tree, parent_is_map)
@@ -2495,11 +2982,16 @@ seq: &seq [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     //EXPECT_FALSE(t.parent_is_map(stream_id));
     EXPECT_FALSE(t.parent_is_map(doc_id));
     EXPECT_TRUE(t.parent_is_map(map_id));
@@ -2512,12 +3004,24 @@ seq: &seq [*valref, bar]
     EXPECT_TRUE(keyval.parent_is_map());
     EXPECT_TRUE(seq.parent_is_map());
     EXPECT_FALSE(val.parent_is_map());
+    //EXPECT_FALSE(mstream.parent_is_map());
+    EXPECT_FALSE(mdoc.parent_is_map());
+    EXPECT_TRUE(mmap.parent_is_map());
+    EXPECT_TRUE(mkeyval.parent_is_map());
+    EXPECT_TRUE(mseq.parent_is_map());
+    EXPECT_FALSE(mval.parent_is_map());
     //EXPECT_EQ(t.parent_is_map(stream_id), stream.parent_is_map());
     EXPECT_EQ(t.parent_is_map(doc_id), doc.parent_is_map());
     EXPECT_EQ(t.parent_is_map(map_id), map.parent_is_map());
     EXPECT_EQ(t.parent_is_map(keyval_id), keyval.parent_is_map());
     EXPECT_EQ(t.parent_is_map(seq_id), seq.parent_is_map());
     EXPECT_EQ(t.parent_is_map(val_id), val.parent_is_map());
+    //EXPECT_EQ(t.parent_is_map(stream_id), mstream.parent_is_map());
+    EXPECT_EQ(t.parent_is_map(doc_id), mdoc.parent_is_map());
+    EXPECT_EQ(t.parent_is_map(map_id), mmap.parent_is_map());
+    EXPECT_EQ(t.parent_is_map(keyval_id), mkeyval.parent_is_map());
+    EXPECT_EQ(t.parent_is_map(seq_id), mseq.parent_is_map());
+    EXPECT_EQ(t.parent_is_map(val_id), mval.parent_is_map());
 }
 
 TEST(Tree, has_parent)
@@ -2532,12 +3036,18 @@ seq: &seq [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_FALSE(t.has_parent(stream_id));
     EXPECT_TRUE(t.has_parent(doc_id));
     EXPECT_TRUE(t.has_parent(map_id));
@@ -2550,12 +3060,24 @@ seq: &seq [*valref, bar]
     EXPECT_TRUE(keyval.has_parent());
     EXPECT_TRUE(seq.has_parent());
     EXPECT_TRUE(val.has_parent());
+    EXPECT_FALSE(mstream.has_parent());
+    EXPECT_TRUE(mdoc.has_parent());
+    EXPECT_TRUE(mmap.has_parent());
+    EXPECT_TRUE(mkeyval.has_parent());
+    EXPECT_TRUE(mseq.has_parent());
+    EXPECT_TRUE(mval.has_parent());
     EXPECT_EQ(t.has_parent(stream_id), stream.has_parent());
     EXPECT_EQ(t.has_parent(doc_id), doc.has_parent());
     EXPECT_EQ(t.has_parent(map_id), map.has_parent());
     EXPECT_EQ(t.has_parent(keyval_id), keyval.has_parent());
     EXPECT_EQ(t.has_parent(seq_id), seq.has_parent());
     EXPECT_EQ(t.has_parent(val_id), val.has_parent());
+    EXPECT_EQ(t.has_parent(stream_id), mstream.has_parent());
+    EXPECT_EQ(t.has_parent(doc_id), mdoc.has_parent());
+    EXPECT_EQ(t.has_parent(map_id), mmap.has_parent());
+    EXPECT_EQ(t.has_parent(keyval_id), mkeyval.has_parent());
+    EXPECT_EQ(t.has_parent(seq_id), mseq.has_parent());
+    EXPECT_EQ(t.has_parent(val_id), mval.has_parent());
 }
 
 
@@ -2574,12 +3096,18 @@ seq: &seq [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_EQ(t.num_children(stream_id), 1u);
     EXPECT_EQ(t.num_children(doc_id), 2u);
     EXPECT_EQ(t.num_children(map_id), 2u);
@@ -2592,6 +3120,12 @@ seq: &seq [*valref, bar]
     EXPECT_EQ(keyval.num_children(), t.num_children(keyval_id));
     EXPECT_EQ(seq.num_children(), t.num_children(seq_id));
     EXPECT_EQ(val.num_children(), t.num_children(val_id));
+    EXPECT_EQ(mstream.num_children(), t.num_children(stream_id));
+    EXPECT_EQ(mdoc.num_children(), t.num_children(doc_id));
+    EXPECT_EQ(mmap.num_children(), t.num_children(map_id));
+    EXPECT_EQ(mkeyval.num_children(), t.num_children(keyval_id));
+    EXPECT_EQ(mseq.num_children(), t.num_children(seq_id));
+    EXPECT_EQ(mval.num_children(), t.num_children(val_id));
 }
 
 TEST(Tree, child)
@@ -2606,12 +3140,18 @@ seq: &seq [*valref, bar]
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
     const size_t val_id = t.first_child(seq_id);
-    const NodeRef stream = t.ref(stream_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
-    const NodeRef keyval = t.ref(keyval_id);
-    const NodeRef seq = t.ref(seq_id);
-    const NodeRef val = t.ref(val_id);
+    ConstNodeRef stream = t.ref(stream_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    ConstNodeRef keyval = t.ref(keyval_id);
+    ConstNodeRef seq = t.ref(seq_id);
+    ConstNodeRef val = t.ref(val_id);
+    NodeRef mstream = t.ref(stream_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
+    NodeRef mkeyval = t.ref(keyval_id);
+    NodeRef mseq = t.ref(seq_id);
+    NodeRef mval = t.ref(val_id);
     EXPECT_EQ(t.child(stream_id, 0), doc_id);
     EXPECT_EQ(t.child(doc_id, 0), map_id);
     EXPECT_EQ(t.child(map_id, 0), keyval_id);
@@ -2624,6 +3164,12 @@ seq: &seq [*valref, bar]
     EXPECT_EQ(keyval.child(0).id(), t.child(keyval_id, 0));
     EXPECT_EQ(seq.child(0).id(), t.child(seq_id, 0));
     EXPECT_EQ(val.child(0).id(), t.child(val_id, 0));
+    EXPECT_EQ(mstream.child(0).id(), t.child(stream_id, 0));
+    EXPECT_EQ(mdoc.child(0).id(), t.child(doc_id, 0));
+    EXPECT_EQ(mmap.child(0).id(), t.child(map_id, 0));
+    EXPECT_EQ(mkeyval.child(0).id(), t.child(keyval_id, 0));
+    EXPECT_EQ(mseq.child(0).id(), t.child(seq_id, 0));
+    EXPECT_EQ(mval.child(0).id(), t.child(val_id, 0));
 }
 
 TEST(Tree, find_child_by_name)
@@ -2637,8 +3183,10 @@ seq: &seq [*valref, bar]
     const size_t map_id = t.first_child(doc_id);
     const size_t keyval_id = t.first_child(map_id);
     const size_t seq_id = t.last_child(doc_id);
-    const NodeRef doc = t.ref(doc_id);
-    const NodeRef map = t.ref(map_id);
+    ConstNodeRef doc = t.ref(doc_id);
+    ConstNodeRef map = t.ref(map_id);
+    NodeRef mdoc = t.ref(doc_id);
+    NodeRef mmap = t.ref(map_id);
     EXPECT_EQ(t.find_child(doc_id, "map"), map_id);
     EXPECT_EQ(t.find_child(doc_id, "seq"), seq_id);
     EXPECT_EQ(t.find_child(doc_id, "..."), (size_t)NONE);
@@ -2649,6 +3197,11 @@ seq: &seq [*valref, bar]
     EXPECT_EQ(doc.find_child("...").id(), t.find_child(doc_id, "..."));
     EXPECT_EQ(map.find_child("foo").id(), t.find_child(map_id, "foo"));
     EXPECT_EQ(map.find_child("bar").id(), t.find_child(map_id, "bar"));
+    EXPECT_EQ(mdoc.find_child("map").id(), t.find_child(doc_id, "map"));
+    EXPECT_EQ(mdoc.find_child("seq").id(), t.find_child(doc_id, "seq"));
+    EXPECT_EQ(mdoc.find_child("...").id(), t.find_child(doc_id, "..."));
+    EXPECT_EQ(mmap.find_child("foo").id(), t.find_child(map_id, "foo"));
+    EXPECT_EQ(mmap.find_child("bar").id(), t.find_child(map_id, "bar"));
 }
 
 
@@ -2717,357 +3270,6 @@ TEST(change_type, from_keyseq)
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
-TEST(NodeRef, 0_general)
-{
-    Tree t;
-
-    NodeRef root(&t);
-
-    //using S = csubstr;
-    //using V = NodeScalar;
-    using N = NodeInit;
-
-    root = N{MAP};
-    root.append_child({"a", "0"});
-    root.append_child({MAP, "b"});
-    root["b"].append_child({SEQ, "seq"});
-    root["b"]["seq"].append_child({"0"});
-    root["b"]["seq"].append_child({"1"});
-    root["b"]["seq"].append_child({"2"});
-    root["b"]["seq"].append_child({NodeScalar{"!!str", "3"}});
-    auto ch4 = root["b"]["seq"][3].append_sibling({"4"});
-    EXPECT_EQ(ch4.id(), root["b"]["seq"][4].id());
-    EXPECT_EQ(ch4.get(), root["b"]["seq"][4].get());
-    EXPECT_EQ((type_bits)root["b"]["seq"][4].type(), (type_bits)VAL);
-    EXPECT_EQ(root["b"]["seq"][4].val(), "4");
-    root["b"]["seq"].append_sibling({NodeScalar{"!!str", "aaa"}, NodeScalar{"!!int", "0"}});
-    EXPECT_EQ((type_bits)root["b"]["seq"][4].type(), (type_bits)VAL);
-    EXPECT_EQ(root["b"]["seq"][4].val(), "4");
-
-    root["b"]["key"] = "val";
-    auto seq = root["b"]["seq"];
-    auto seq2 = root["b"]["seq2"];
-    EXPECT_TRUE(seq2.is_seed());
-    root["b"]["seq2"] = N(SEQ);
-    seq2 = root["b"]["seq2"];
-    EXPECT_FALSE(seq2.is_seed());
-    EXPECT_TRUE(seq2.is_seq());
-    EXPECT_EQ(seq2.num_children(), 0);
-    EXPECT_EQ(root["b"]["seq2"].get(), seq2.get());
-    auto seq20 = seq2[0];
-    EXPECT_TRUE(seq20.is_seed());
-    EXPECT_TRUE(seq2[0].is_seed());
-    EXPECT_EQ(seq2.num_children(), 0);
-    EXPECT_TRUE(seq2[0].is_seed());
-    EXPECT_TRUE(seq20.is_seed());
-    EXPECT_NE(seq.get(), seq2.get());
-    seq20 = root["b"]["seq2"][0];
-    EXPECT_TRUE(seq20.is_seed());
-    root["b"]["seq2"][0] = "00";
-    seq20 = root["b"]["seq2"][0];
-    EXPECT_FALSE(seq20.is_seed());
-    NodeRef before = root["b"]["key"];
-    EXPECT_EQ(before.key(), "key");
-    EXPECT_EQ(before.val(), "val");
-    root["b"]["seq2"][1] = "01";
-    NodeRef after = root["b"]["key"];
-    EXPECT_EQ(before.key(), "key");
-    EXPECT_EQ(before.val(), "val");
-    EXPECT_EQ(after.key(), "key");
-    EXPECT_EQ(after.val(), "val");
-    root["b"]["seq2"][2] = "02";
-    root["b"]["seq2"][3] = "03";
-    int iv = 0;
-    root["b"]["seq2"][4] << 55; root["b"]["seq2"][4] >> iv;
-    size_t zv = 0;
-    root["b"]["seq2"][5] << size_t(55); root["b"]["seq2"][5] >> zv;
-    float fv = 0;
-    root["b"]["seq2"][6] << 2.0f; root["b"]["seq2"][6] >> fv;
-    float dv = 0;
-    root["b"]["seq2"][7] << 2.0; root["b"]["seq2"][7] >> dv;
-
-    EXPECT_EQ(root["b"]["key"].key(), "key");
-    EXPECT_EQ(root["b"]["key"].val(), "val");
-
-
-    emit(t);
-
-    EXPECT_TRUE(root.type().is_map());
-    EXPECT_TRUE(root["a"].type().is_keyval());
-    EXPECT_EQ(root["a"].key(), "a");
-    EXPECT_EQ(root["a"].val(), "0");
-
-    EXPECT_TRUE(root["b"].type().has_key());
-    EXPECT_TRUE(root["b"].type().is_map());
-
-    EXPECT_TRUE(root["b"]["seq"].type().has_key());
-    EXPECT_TRUE(root["b"]["seq"].type().is_seq());
-    EXPECT_EQ  (root["b"]["seq"].key(), "seq");
-    EXPECT_TRUE(root["b"]["seq"][0].type().is_val());
-    EXPECT_EQ(  root["b"]["seq"][0].val(), "0");
-    EXPECT_TRUE(root["b"]["seq"][1].type().is_val());
-    EXPECT_EQ(  root["b"]["seq"][1].val(), "1");
-    EXPECT_TRUE(root["b"]["seq"][2].type().is_val());
-    EXPECT_EQ(  root["b"]["seq"][2].val(), "2");
-    EXPECT_TRUE(root["b"]["seq"][3].type().is_val());
-    EXPECT_EQ(  root["b"]["seq"][3].val(), "3");
-    EXPECT_EQ(  root["b"]["seq"][3].val_tag(), "!!str");
-    EXPECT_TRUE(root["b"]["seq"][4].type().is_val());
-    EXPECT_EQ(  root["b"]["seq"][4].val(), "4");
-
-    int tv;
-    EXPECT_EQ(root["b"]["key"].key(), "key");
-    EXPECT_EQ(root["b"]["key"].val(), "val");
-    EXPECT_EQ(root["b"]["seq2"][0].val(), "00"); root["b"]["seq2"][0] >> tv; EXPECT_EQ(tv, 0);
-    EXPECT_EQ(root["b"]["seq2"][1].val(), "01"); root["b"]["seq2"][1] >> tv; EXPECT_EQ(tv, 1);
-    EXPECT_EQ(root["b"]["seq2"][2].val(), "02"); root["b"]["seq2"][2] >> tv; EXPECT_EQ(tv, 2);
-    EXPECT_EQ(root["b"]["seq2"][3].val(), "03"); root["b"]["seq2"][3] >> tv; EXPECT_EQ(tv, 3);
-    EXPECT_EQ(root["b"]["seq2"][4].val(), "55"); EXPECT_EQ(iv, 55);
-    EXPECT_EQ(root["b"]["seq2"][5].val(), "55"); EXPECT_EQ(zv, size_t(55));
-    EXPECT_EQ(root["b"]["seq2"][6].val(), "2"); EXPECT_EQ(fv, 2.f);
-    EXPECT_EQ(root["b"]["seq2"][6].val(), "2"); EXPECT_EQ(dv, 2.);
-
-    root["b"]["seq"][2].set_val_serialized(22);
-
-    emit(t);
-
-    EXPECT_TRUE(root["b"]["aaa"].type().is_keyval());
-    EXPECT_EQ(root["b"]["aaa"].key_tag(), "!!str");
-    EXPECT_EQ(root["b"]["aaa"].key(), "aaa");
-    EXPECT_EQ(root["b"]["aaa"].val_tag(), "!!int");
-    EXPECT_EQ(root["b"]["aaa"].val(), "0");
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-void noderef_check_tree(NodeRef const& root)
-{
-    test_invariants(*root.tree());
-
-    EXPECT_EQ(root.tree()->size(), 7u);
-    EXPECT_EQ(root.num_children(), 6u);
-    EXPECT_EQ(root.is_container(), true);
-    EXPECT_EQ(root.is_seq(), true);
-
-    EXPECT_TRUE(root[0].type().is_val());
-    EXPECT_EQ(  root[0].val(), "0");
-    EXPECT_TRUE(root[1].type().is_val());
-    EXPECT_EQ(  root[1].val(), "1");
-    EXPECT_TRUE(root[2].type().is_val());
-    EXPECT_EQ(  root[2].val(), "2");
-    EXPECT_TRUE(root[3].type().is_val());
-    EXPECT_EQ(  root[3].val(), "3");
-    EXPECT_TRUE(root[4].type().is_val());
-    EXPECT_EQ(  root[4].val(), "4");
-    EXPECT_TRUE(root[5].type().is_val());
-    EXPECT_EQ(  root[5].val(), "5");
-}
-
-TEST(NodeRef, 1_append_child)
-{
-    Tree t;
-
-    NodeRef root(&t);
-
-    root |= SEQ;
-    root.append_child({"0"});
-    root.append_child({"1"});
-    root.append_child({"2"});
-    root.append_child({"3"});
-    root.append_child({"4"});
-    root.append_child({"5"});
-
-    noderef_check_tree(root);
-}
-
-TEST(NodeRef, 2_prepend_child)
-{
-    Tree t;
-
-    NodeRef root(&t);
-
-    root |= SEQ;
-    root.prepend_child({"5"});
-    root.prepend_child({"4"});
-    root.prepend_child({"3"});
-    root.prepend_child({"2"});
-    root.prepend_child({"1"});
-    root.prepend_child({"0"});
-
-    noderef_check_tree(root);
-}
-
-TEST(NodeRef, 3_insert_child)
-{
-    Tree t;
-
-    NodeRef root(&t);
-    NodeRef none(&t, NONE);
-
-    root |= SEQ;
-    root.insert_child({"3"}, none);
-    root.insert_child({"4"}, root[0]);
-    root.insert_child({"0"}, none);
-    root.insert_child({"5"}, root[2]);
-    root.insert_child({"1"}, root[0]);
-    root.insert_child({"2"}, root[1]);
-
-    noderef_check_tree(root);
-}
-
-TEST(NodeRef, 4_remove_child)
-{
-    Tree t;
-
-    NodeRef root(&t);
-    NodeRef none(&t, NONE);
-
-    root |= SEQ;
-    root.insert_child({"3"}, none);
-    root.insert_child({"4"}, root[0]);
-    root.insert_child({"0"}, none);
-    root.insert_child({"5"}, root[2]);
-    root.insert_child({"1"}, root[0]);
-    root.insert_child({"2"}, root[1]);
-
-    std::vector<int> vec({10, 20, 30, 40, 50, 60, 70, 80, 90});
-    root.insert_child(root[0]) << vec; // 1
-    root.insert_child(root[2]) << vec; // 3
-    root.insert_child(root[4]) << vec; // 5
-    root.insert_child(root[6]) << vec; // 7
-    root.insert_child(root[8]) << vec; // 9
-    root.append_child() << vec;        // 10
-
-    root.remove_child(11);
-    root.remove_child(9);
-    root.remove_child(7);
-    root.remove_child(5);
-    root.remove_child(3);
-    root.remove_child(1);
-
-    noderef_check_tree(root);
-
-    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
-    root.prepend_child() << vec2; // 0
-    root.insert_child(root[1]) << vec2; // 2
-    root.insert_child(root[3]) << vec2; // 4
-    root.insert_child(root[5]) << vec2; // 6
-    root.insert_child(root[7]) << vec2; // 8
-    root.insert_child(root[9]) << vec2; // 10
-    root.append_child() << vec2;        // 12
-
-    root.remove_child(12);
-    root.remove_child(10);
-    root.remove_child(8);
-    root.remove_child(6);
-    root.remove_child(4);
-    root.remove_child(2);
-    root.remove_child(0);
-
-    noderef_check_tree(root);
-}
-
-TEST(NodeRef, 5_move_in_same_parent)
-{
-    Tree t;
-    NodeRef r = t;
-
-    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
-    std::map<std::string, int> map2({{"foo", 100}, {"bar", 200}, {"baz", 300}});
-
-    r |= SEQ;
-    r.append_child() << vec2;
-    r.append_child() << map2;
-    r.append_child() << "elm2";
-    r.append_child() << "elm3";
-
-    auto s = r[0];
-    auto m = r[1];
-    EXPECT_TRUE(s.is_seq());
-    EXPECT_TRUE(m.is_map());
-    EXPECT_EQ(s.num_children(), vec2.size());
-    EXPECT_EQ(m.num_children(), map2.size());
-    //printf("fonix"); print_tree(t); emit(r);
-    r[0].move(r[1]);
-    //printf("fonix"); print_tree(t); emit(r);
-    EXPECT_EQ(r[0].get(), m.get());
-    EXPECT_EQ(r[0].num_children(), map2.size());
-    EXPECT_EQ(r[1].get(), s.get());
-    EXPECT_EQ(r[1].num_children(), vec2.size());
-}
-
-TEST(NodeRef, 6_move_to_other_parent)
-{
-    Tree t;
-    NodeRef r = t;
-
-    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
-    std::map<std::string, int> map2({{"foo", 100}, {"bar", 200}, {"baz", 300}});
-
-    r |= SEQ;
-    r.append_child() << vec2;
-    r.append_child() << map2;
-    r.append_child() << "elm2";
-    r.append_child() << "elm3";
-
-    NodeData *elm2 = r[2].get();
-    EXPECT_EQ(r[2].val(), "elm2");
-    //printf("fonix"); print_tree(t); emit(r);
-    r[2].move(r[0], r[0][0]);
-    EXPECT_EQ(r[0][1].get(), elm2);
-    EXPECT_EQ(r[0][1].val(), "elm2");
-    //printf("fonix"); print_tree(t); emit(r);
-}
-
-TEST(NodeRef, 7_duplicate)
-{
-    Tree t;
-    NodeRef r = t;
-
-    std::vector<std::vector<int>> vec2({{100, 200}, {300, 400}, {500, 600}, {700, 800, 900}});
-    std::map<std::string, int> map2({{"bar", 200}, {"baz", 300}, {"foo", 100}});
-
-    r |= SEQ;
-    r.append_child() << vec2;
-    r.append_child() << map2;
-    r.append_child() << "elm2";
-    r.append_child() << "elm3";
-
-    EXPECT_EQ(r[0][0].num_children(), 2u);
-    NodeRef dup = r[1].duplicate(r[0][0], r[0][0][1]);
-    EXPECT_EQ(r[0][0].num_children(), 3u);
-    EXPECT_EQ(r[0][0][2].num_children(), map2.size());
-    EXPECT_NE(dup.get(), r[1].get());
-    EXPECT_EQ(dup[0].key(), "bar");
-    EXPECT_EQ(dup[0].val(), "200");
-    EXPECT_EQ(dup[1].key(), "baz");
-    EXPECT_EQ(dup[1].val(), "300");
-    EXPECT_EQ(dup[2].key(), "foo");
-    EXPECT_EQ(dup[2].val(), "100");
-    EXPECT_EQ(dup[0].key().str, r[1][0].key().str);
-    EXPECT_EQ(dup[0].val().str, r[1][0].val().str);
-    EXPECT_EQ(dup[0].key().len, r[1][0].key().len);
-    EXPECT_EQ(dup[0].val().len, r[1][0].val().len);
-    EXPECT_EQ(dup[1].key().str, r[1][1].key().str);
-    EXPECT_EQ(dup[1].val().str, r[1][1].val().str);
-    EXPECT_EQ(dup[1].key().len, r[1][1].key().len);
-    EXPECT_EQ(dup[1].val().len, r[1][1].val().len);
-}
-
-TEST(NodeRef, intseq)
-{
-    Tree t = parse_in_arena("iseq: [8, 10]");
-    NodeRef n = t["iseq"];
-    int a, b;
-    n[0] >> a;
-    n[1] >> b;
-    EXPECT_EQ(a, 8);
-    EXPECT_EQ(b, 10);
-}
-
-
 TEST(Tree, lookup_path)
 {
     const char yaml[] = R"(
@@ -3600,7 +3802,7 @@ doc4
     }
     {
         const Tree &ct = t;
-        const NodeRef r = ct.rootref();
+        const ConstNodeRef r = ct.rootref();
         EXPECT_EQ(r.id(), ir);
         EXPECT_EQ(r.child(0), r.doc(0));
         EXPECT_EQ(r.child(1), r.doc(1));