diff --git a/c++/src/capnproto/encoding-test.c++ b/c++/src/capnproto/encoding-test.c++
index 80aa71a056caa6098672cb07e821dba336f865d3..a1638a2462d05ba033884c3ebd8195336b2b097b 100644
--- a/c++/src/capnproto/encoding-test.c++
+++ b/c++/src/capnproto/encoding-test.c++
@@ -39,27 +39,107 @@ TEST(Encoding, Simple) {
   EXPECT_EQ(1234, builder.getA());
   EXPECT_EQ(-1, builder.getB());
   EXPECT_EQ(200, builder.getC());
-  ASSERT_EQ(0, builder.getNums().size());
+  ASSERT_EQ(0u, builder.getNums().size());
 
   builder.setA(321);
   builder.setB(45);
   builder.setC(67);
   builder.initD().setX(55.25);
 
-  List<int32_t>::Builder listBuilder = builder.initNums(5);
-  ASSERT_EQ(5, listBuilder.size());
-  listBuilder[0] = 12;
-  listBuilder[1] = 34;
-  listBuilder[2] = 56;
-  listBuilder[3] = 78;
-  listBuilder[4] = 90;
+  {
+    List<int32_t>::Builder listBuilder = builder.initNums(5);
+    ASSERT_EQ(5u, listBuilder.size());
+    listBuilder.set(0, 12);
+    listBuilder.set(1, 34);
+    listBuilder.set(2, 56);
+    listBuilder.set(3, 78);
+    listBuilder.set(4, 90);
+
+    EXPECT_EQ(12, listBuilder[0]);
+    EXPECT_EQ(34, listBuilder[1]);
+    EXPECT_EQ(56, listBuilder[2]);
+    EXPECT_EQ(78, listBuilder[3]);
+    EXPECT_EQ(90, listBuilder[4]);
+
+    {
+      int sum = 0;
+      for (int32_t i: listBuilder) {
+        sum += i;
+      }
+      EXPECT_EQ(12 + 34 + 56 + 78 + 90, sum);
+    }
+  }
+
+  {
+    List<Bar>::Builder structListBuilder = builder.initBars(3);
+    ASSERT_EQ(3u, structListBuilder.size());
+
+    structListBuilder[0].setX(123);
+    structListBuilder[1].setX(456);
+    structListBuilder[2].setX(789);
+
+    EXPECT_EQ(123, structListBuilder[0].getX());
+    EXPECT_EQ(456, structListBuilder[1].getX());
+    EXPECT_EQ(789, structListBuilder[2].getX());
+
+    {
+      double sum = 0;
+      for (auto bar: structListBuilder) {
+        sum += bar.getX();
+      }
+      EXPECT_EQ(123 + 456 + 789, sum);
+    }
+  }
 
   {
-    int sum = 0;
-    for (int32_t i: listBuilder) {
-      sum += i;
+    List<Bar>::Builder structListBuilder = builder.getBars();
+    ASSERT_EQ(3u, structListBuilder.size());
+
+    EXPECT_EQ(123, structListBuilder[0].getX());
+    EXPECT_EQ(456, structListBuilder[1].getX());
+    EXPECT_EQ(789, structListBuilder[2].getX());
+
+    {
+      double sum = 0;
+      for (auto bar: structListBuilder) {
+        sum += bar.getX();
+      }
+      EXPECT_EQ(123 + 456 + 789, sum);
     }
-    EXPECT_EQ(12 + 34 + 56 + 78 + 90, sum);
+  }
+
+  {
+    List<List<int32_t>>::Builder listListBuilder = builder.initPrimListList(2);
+    ASSERT_EQ(2u, listListBuilder.size());
+
+    List<int32_t>::Builder sublist = listListBuilder.init(0, 2);
+    ASSERT_EQ(2u, sublist.size());
+    sublist.set(0, 1234);
+    sublist.set(1, 5678);
+
+    sublist = listListBuilder.init(1, 4);
+    ASSERT_EQ(4u, sublist.size());
+    sublist.set(0, 21);
+    sublist.set(1, 43);
+    sublist.set(2, 65);
+    sublist.set(3, 87);
+  }
+
+  {
+    List<List<Bar>>::Builder listListBuilder = builder.initStructListList(2);
+    ASSERT_EQ(2u, listListBuilder.size());
+
+    List<Bar>::Builder sublist = listListBuilder.init(0, 2);
+    ASSERT_EQ(2u, sublist.size());
+    sublist[0].setX(1234);
+    sublist[1].setX(5678);
+
+    sublist = listListBuilder.init(1, 4);
+    ASSERT_EQ(4u, sublist.size());
+    sublist[0].setX(21);
+    sublist[1].setX(43);
+    sublist[2].setX(65);
+    sublist[3].setX(87);
   }
 
   EXPECT_EQ(321, builder.getA());
@@ -75,20 +155,73 @@ TEST(Encoding, Simple) {
   EXPECT_EQ(67, reader.getC());
   EXPECT_EQ(55.25, reader.getD().getX());
 
-  List<int32_t>::Reader listReader = reader.getNums();
-  ASSERT_EQ(5, listReader.size());
-  EXPECT_EQ(12, listReader[0]);
-  EXPECT_EQ(34, listReader[1]);
-  EXPECT_EQ(56, listReader[2]);
-  EXPECT_EQ(78, listReader[3]);
-  EXPECT_EQ(90, listReader[4]);
+  {
+    List<int32_t>::Reader listReader = reader.getNums();
+    ASSERT_EQ(5u, listReader.size());
+    EXPECT_EQ(12, listReader[0]);
+    EXPECT_EQ(34, listReader[1]);
+    EXPECT_EQ(56, listReader[2]);
+    EXPECT_EQ(78, listReader[3]);
+    EXPECT_EQ(90, listReader[4]);
+
+    {
+      int sum = 0;
+      for (int32_t i: listReader) {
+        sum += i;
+      }
+      EXPECT_EQ(12 + 34 + 56 + 78 + 90, sum);
+    }
+  }
 
   {
-    int sum = 0;
-    for (int32_t i: listReader) {
-      sum += i;
+    List<Bar>::Reader structListReader = reader.getBars();
+    ASSERT_EQ(3u, structListReader.size());
+
+    EXPECT_EQ(123, structListReader[0].getX());
+    EXPECT_EQ(456, structListReader[1].getX());
+    EXPECT_EQ(789, structListReader[2].getX());
+
+    {
+      double sum = 0;
+      for (auto bar: structListReader) {
+        sum += bar.getX();
+      }
+      EXPECT_EQ(123 + 456 + 789, sum);
     }
-    EXPECT_EQ(12 + 34 + 56 + 78 + 90, sum);
+  }
+
+  {
+    List<List<int32_t>>::Reader listListReader = reader.getPrimListList();
+    ASSERT_EQ(2u, listListReader.size());
+
+    List<int32_t>::Reader sublist = listListReader[0];
+    ASSERT_EQ(2u, sublist.size());
+    EXPECT_EQ(1234, sublist[0]);
+    EXPECT_EQ(5678, sublist[1]);
+
+    sublist = listListReader[1];
+    ASSERT_EQ(4u, sublist.size());
+    EXPECT_EQ(21, sublist[0]);
+    EXPECT_EQ(43, sublist[1]);
+    EXPECT_EQ(65, sublist[2]);
+    EXPECT_EQ(87, sublist[3]);
+  }
+
+  {
+    List<List<Bar>>::Reader listListReader = reader.getStructListList();
+    ASSERT_EQ(2u, listListReader.size());
+
+    List<Bar>::Reader sublist = listListReader[0];
+    ASSERT_EQ(2u, sublist.size());
+    EXPECT_EQ(1234, sublist[0].getX());
+    EXPECT_EQ(5678, sublist[1].getX());
+
+    sublist = listListReader[1];
+    ASSERT_EQ(4u, sublist.size());
+    EXPECT_EQ(21, sublist[0].getX());
+    EXPECT_EQ(43, sublist[1].getX());
+    EXPECT_EQ(65, sublist[2].getX());
+    EXPECT_EQ(87, sublist[3].getX());
   }
 }
 
diff --git a/c++/src/capnproto/list.h b/c++/src/capnproto/list.h
index 0c2df00e51c263bedebe2f2ef00b84f9931ee0a3..1264a8e46c12e1dfc129dd4992f35c4854276ec8 100644
--- a/c++/src/capnproto/list.h
+++ b/c++/src/capnproto/list.h
@@ -25,9 +25,17 @@
 #define CAPNPROTO_LIST_H_
 
 #include "wire-format.h"
+#include "descriptor.h"     // only for FieldSize; TODO:  Eliminate this
 
 namespace capnproto {
 
+namespace internal {
+template <typename T> struct IsPrimitive;
+}  // namespace internal
+
+template <typename T, bool isPrimitive = internal::IsPrimitive<T>::value>
+struct List;
+
 namespace internal {
 
 template <typename T> struct IsPrimitive { static constexpr bool value = false; };
@@ -44,6 +52,27 @@ template <> struct IsPrimitive<uint32_t> { static constexpr bool value = true; }
 template <> struct IsPrimitive<uint64_t> { static constexpr bool value = true; };
 template <> struct IsPrimitive<float>    { static constexpr bool value = true; };
 template <> struct IsPrimitive<double>   { static constexpr bool value = true; };
+template <typename T, bool b> struct IsPrimitive<List<T, b>> {
+  static constexpr bool value = IsPrimitive<T>::value;
+};
+
+template <typename T> struct FieldSizeForType { static constexpr FieldSize value = FieldSize::INLINE_COMPOSITE; };
+
+template <> struct FieldSizeForType<void>     { static constexpr FieldSize value = FieldSize::VOID; };
+template <> struct FieldSizeForType<bool>     { static constexpr FieldSize value = FieldSize::BIT; };
+template <> struct FieldSizeForType<int8_t>   { static constexpr FieldSize value = FieldSize::BYTE; };
+template <> struct FieldSizeForType<int16_t>  { static constexpr FieldSize value = FieldSize::TWO_BYTES; };
+template <> struct FieldSizeForType<int32_t>  { static constexpr FieldSize value = FieldSize::FOUR_BYTES; };
+template <> struct FieldSizeForType<int64_t>  { static constexpr FieldSize value = FieldSize::EIGHT_BYTES; };
+template <> struct FieldSizeForType<uint8_t>  { static constexpr FieldSize value = FieldSize::BYTE; };
+template <> struct FieldSizeForType<uint16_t> { static constexpr FieldSize value = FieldSize::TWO_BYTES; };
+template <> struct FieldSizeForType<uint32_t> { static constexpr FieldSize value = FieldSize::FOUR_BYTES; };
+template <> struct FieldSizeForType<uint64_t> { static constexpr FieldSize value = FieldSize::EIGHT_BYTES; };
+template <> struct FieldSizeForType<float>    { static constexpr FieldSize value = FieldSize::FOUR_BYTES; };
+template <> struct FieldSizeForType<double>   { static constexpr FieldSize value = FieldSize::EIGHT_BYTES; };
+template <typename T, bool b> struct FieldSizeForType<List<T, b>> {
+  static constexpr FieldSize value = FieldSize::REFERENCE;
+};
 
 template<typename T> constexpr T&& move(T& t) noexcept { return static_cast<T&&>(t); }
 // Like std::move.  Unfortunately, #including <utility> brings in tons of unnecessary stuff.
@@ -64,15 +93,17 @@ private:
   T value;
 };
 
-template <typename Container, typename Element, typename Reference>
+template <typename Container, typename Element>
 class IndexingIterator {
 public:
   IndexingIterator() = default;
 
-  inline Reference operator*() { return (*container)[index]; }
-  inline Reference operator->() { return TemporaryPointer<Element>((*container)[index]); }
-  inline Reference operator[]( int off) { return (*container)[index]; }
-  inline Reference operator[](uint off) { return (*container)[index]; }
+  inline Element operator*() { return (*container)[index]; }
+  inline TemporaryPointer<Element> operator->() {
+    return TemporaryPointer<Element>((*container)[index]);
+  }
+  inline Element operator[]( int off) { return (*container)[index]; }
+  inline Element operator[](uint off) { return (*container)[index]; }
 
   inline IndexingIterator& operator++() { ++index; return *this; }
   inline IndexingIterator operator++(int) { IndexingIterator other = *this; ++index; return other; }
@@ -105,14 +136,11 @@ private:
   uint index;
 
   friend Container;
-  IndexingIterator(Container* builder, uint index): container(container), index(index) {}
+  inline IndexingIterator(Container* container, uint index): container(container), index(index) {}
 };
 
 }  // namespace internal
 
-template <typename T, bool isPrimitive = internal::IsPrimitive<T>::value>
-struct List;
-
 template <typename T>
 struct List<T, true> {
   class Reader {
@@ -120,11 +148,10 @@ struct List<T, true> {
     Reader() = default;
     inline explicit Reader(internal::ListReader reader): reader(reader) {}
 
-    typedef internal::IndexingIterator<Reader, T, T> iterator;
-
-    inline T operator[](uint index) { return reader.template getDataElement<T>(index * ELEMENTS); }
     inline uint size() { return reader.size() / ELEMENTS; }
+    inline T operator[](uint index) { return reader.template getDataElement<T>(index * ELEMENTS); }
 
+    typedef internal::IndexingIterator<Reader, T> iterator;
     inline iterator begin() { return iterator(this, 0); }
     inline iterator end() { return iterator(this, size()); }
 
@@ -137,36 +164,62 @@ struct List<T, true> {
     Builder() = default;
     inline explicit Builder(internal::ListBuilder builder): builder(builder) {}
 
-    class reference {
-    public:
-      reference() = default;
+    inline uint size() { return builder.size() / ELEMENTS; }
+    inline T operator[](uint index) {
+      return builder.template getDataElement<T>(index * ELEMENTS);
+    }
+    inline void set(uint index, T value) {
+      // Alas, it is not possible to make operator[] return a reference to which you can assign,
+      // since the encoded representation does not necessarily match the compiler's representation
+      // of the type.  We can't even return a clever class that implements operator T() and
+      // operator=() because it will lead to surprising behavior when using type inference (e.g.
+      // calling a template function with inferred argument types, or using "auto" or "decltype").
+
+      builder.template setDataElement<T>(index * ELEMENTS, value);
+    }
 
-      inline operator T() { return (*builder)[index]; }
-      inline reference& operator=(T value) {
-        builder->builder.template setDataElement<T>(index * ELEMENTS, value);
-        return *this;
-      }
+    typedef internal::IndexingIterator<Builder, T> iterator;
+    inline iterator begin() { return iterator(this, 0); }
+    inline iterator end() { return iterator(this, size()); }
 
-      T* operator&() {
-        static_assert(sizeof(T) < 0,
-            "You can't take the address of a list member because they are not stored in memory "
-            "in a directly-usable format.");
-        return nullptr;
-      }
+  private:
+    internal::ListBuilder builder;
+  };
+};
+
+template <typename T>
+struct List<T, false> {
+  class Reader {
+  public:
+    Reader() = default;
+    inline explicit Reader(internal::ListReader reader): reader(reader) {}
+
+    inline uint size() { return reader.size() / ELEMENTS; }
+    inline typename T::Reader operator[](uint index) {
+      return typename T::Reader(reader.getStructElement(index * ELEMENTS, T::DEFAULT.words));
+    }
 
-    private:
-      Builder* builder;
-      uint index;
+    typedef internal::IndexingIterator<Reader, typename T::Reader> iterator;
+    inline iterator begin() { return iterator(this, 0); }
+    inline iterator end() { return iterator(this, size()); }
 
-      friend class Builder;
-      reference(Builder* builder, uint index): builder(builder), index(index) {}
-    };
+  private:
+    internal::ListReader reader;
+  };
 
-    typedef internal::IndexingIterator<Builder, T, reference> iterator;
+  class Builder {
+  public:
+    Builder() = default;
+    inline explicit Builder(internal::ListBuilder builder): builder(builder) {}
 
-    inline reference operator[](uint index) { return reference(this, index); }
     inline uint size() { return builder.size() / ELEMENTS; }
+    inline typename T::Builder operator[](uint index) {
+      return typename T::Builder(builder.getStructElement(index * ELEMENTS,
+          (T::DATA_SIZE + T::REFERENCE_COUNT * WORDS_PER_REFERENCE) / ELEMENTS,
+          T::DATA_SIZE));
+    }
 
+    typedef internal::IndexingIterator<Builder, typename T::Builder> iterator;
     inline iterator begin() { return iterator(this, 0); }
     inline iterator end() { return iterator(this, size()); }
 
@@ -176,19 +229,19 @@ struct List<T, true> {
 };
 
 template <typename T>
-struct List<T, false> {
+struct List<List<T>, true> {
   class Reader {
   public:
     Reader() = default;
     inline explicit Reader(internal::ListReader reader): reader(reader) {}
 
-    typedef internal::IndexingIterator<Reader, typename T::Reader, typename T::Reader> iterator;
-
-    inline typename T::Reader operator[](uint index) {
-      return typename T::Reader(reader.getStructElement(index * ELEMENTS, T::DEFAULT.words));
-    }
     inline uint size() { return reader.size() / ELEMENTS; }
+    inline typename List<T>::Reader operator[](uint index) {
+      return typename List<T>::Reader(reader.getListElement(index * REFERENCES,
+          internal::FieldSizeForType<T>::value));
+    }
 
+    typedef internal::IndexingIterator<Reader, typename List<T>::Reader> iterator;
     inline iterator begin() { return iterator(this, 0); }
     inline iterator end() { return iterator(this, size()); }
 
@@ -201,38 +254,60 @@ struct List<T, false> {
     Builder() = default;
     inline explicit Builder(internal::ListBuilder builder): builder(builder) {}
 
-    class reference {
-    public:
-      reference() = default;
+    inline uint size() { return builder.size() / ELEMENTS; }
+    inline typename List<T>::Builder operator[](uint index) {
+      return typename List<T>::Builder(builder.getListElement(index * REFERENCES));
+    }
+    inline typename List<T>::Builder init(uint index, uint size) {
+      return typename List<T>::Builder(builder.initListElement(
+          index * REFERENCES, internal::FieldSizeForType<T>::value, size * ELEMENTS));
+    }
 
-      inline operator typename T::Builder() { return (*builder)[index]; }
+    typedef internal::IndexingIterator<Builder, typename List<T>::Builder> iterator;
+    inline iterator begin() { return iterator(this, 0); }
+    inline iterator end() { return iterator(this, size()); }
 
-      // TODO:  operator= to accept ownership transfer.
+  private:
+    internal::ListBuilder builder;
+  };
+};
+
+template <typename T>
+struct List<List<T>, false> {
+  class Reader {
+  public:
+    Reader() = default;
+    inline explicit Reader(internal::ListReader reader): reader(reader) {}
 
-      T* operator&() {
-        static_assert(sizeof(T) < 0,
-            "You can't take the address of a list member because they are not stored in memory "
-            "in a directly-usable format.");
-        return nullptr;
-      }
+    inline uint size() { return reader.size() / ELEMENTS; }
+    inline typename List<T>::Reader operator[](uint index) {
+      return typename List<T>::Reader(reader.getListElement(index * REFERENCES,
+          internal::FieldSizeForType<T>::value));
+    }
 
-    private:
-      Builder* builder;
-      uint index;
+    typedef internal::IndexingIterator<Reader, typename List<T>::Reader> iterator;
+    inline iterator begin() { return iterator(this, 0); }
+    inline iterator end() { return iterator(this, size()); }
 
-      friend class Builder;
-      reference(Builder* builder, uint index): builder(builder), index(index) {}
-    };
+  private:
+    internal::ListReader reader;
+  };
 
-    typedef internal::IndexingIterator<Builder, typename T::Builder, reference> iterator;
+  class Builder {
+  public:
+    Builder() = default;
+    inline explicit Builder(internal::ListBuilder builder): builder(builder) {}
 
-    inline typename T::Builder operator[](uint index) {
-      return typename T::Builder(builder.getStructElement(index * ELEMENTS,
-          (T::DATA_SIZE + T::REFERENCE_COUNT * WORDS_PER_REFERENCE) / ELEMENTS,
-          T::DATA_SIZE));
-    }
     inline uint size() { return builder.size() / ELEMENTS; }
+    inline typename List<T>::Builder operator[](uint index) {
+      return typename List<T>::Builder(builder.getListElement(index * REFERENCES));
+    }
+    inline typename List<T>::Builder init(uint index, uint size) {
+      return typename List<T>::Builder(builder.initStructListElement(
+          index * REFERENCES, size * ELEMENTS, T::DEFAULT.words));
+    }
 
+    typedef internal::IndexingIterator<Builder, typename List<T>::Builder> iterator;
     inline iterator begin() { return iterator(this, 0); }
     inline iterator end() { return iterator(this, size()); }
 
diff --git a/c++/src/capnproto/wire-format-test.c++ b/c++/src/capnproto/wire-format-test.c++
index 0c6686fec52749e9a3a89d864f77a54d05dd2999..9b01ee72237de135e5c4ac953be6a01bc2497da4 100644
--- a/c++/src/capnproto/wire-format-test.c++
+++ b/c++/src/capnproto/wire-format-test.c++
@@ -200,7 +200,7 @@ static void checkStruct(StructBuilder builder) {
     ListBuilder list = builder.getListField(3 * REFERENCES, nullptr);
     ASSERT_EQ(5 * ELEMENTS, list.size());
     for (uint i = 0; i < 5; i++) {
-      ListBuilder element = list.getListElement(i * REFERENCES, FieldSize::TWO_BYTES);
+      ListBuilder element = list.getListElement(i * REFERENCES);
       ASSERT_EQ((i + 1) * ELEMENTS, element.size());
       for (uint j = 0; j <= i; j++) {
         EXPECT_EQ(500u + j, element.getDataElement<uint16_t>(j * ELEMENTS));
@@ -253,7 +253,7 @@ static void checkStruct(StructReader reader) {
     ListReader list = reader.getListField(3 * REFERENCES, FieldSize::REFERENCE, nullptr);
     ASSERT_EQ(5 * ELEMENTS, list.size());
     for (uint i = 0; i < 5; i++) {
-      ListReader element = list.getListElement(i * REFERENCES, FieldSize::TWO_BYTES, nullptr);
+      ListReader element = list.getListElement(i * REFERENCES, FieldSize::TWO_BYTES);
       ASSERT_EQ((i + 1) * ELEMENTS, element.size());
       for (uint j = 0; j <= i; j++) {
         EXPECT_EQ(500u + j, element.getDataElement<uint16_t>(j * ELEMENTS));
diff --git a/c++/src/capnproto/wire-format.c++ b/c++/src/capnproto/wire-format.c++
index 033bb3cbf851f1043568e08a3c206e0120e29f7a..ee549abfdc3a5306a1de4477ba627ebf99ec1e52 100644
--- a/c++/src/capnproto/wire-format.c++
+++ b/c++/src/capnproto/wire-format.c++
@@ -863,7 +863,7 @@ ListBuilder ListBuilder::initStructListElement(
       elementCount, elementDefaultValue);
 }
 
-ListBuilder ListBuilder::getListElement(WireReferenceCount index, FieldSize elementSize) const {
+ListBuilder ListBuilder::getListElement(WireReferenceCount index) const {
   return WireHelpers::getWritableListReference(
       reinterpret_cast<WireReference*>(ptr) + index, segment, nullptr);
 }
@@ -900,10 +900,10 @@ StructReader ListReader::getStructElement(ElementCount index, const word* defaul
 }
 
 ListReader ListReader::getListElement(
-    WireReferenceCount index, FieldSize expectedElementSize, const word* defaultValue) const {
+    WireReferenceCount index, FieldSize expectedElementSize) const {
   return WireHelpers::readListReference(
       segment, reinterpret_cast<const WireReference*>(ptr) + index,
-      defaultValue, expectedElementSize, recursionLimit);
+      nullptr, expectedElementSize, recursionLimit);
 }
 
 }  // namespace internal
diff --git a/c++/src/capnproto/wire-format.h b/c++/src/capnproto/wire-format.h
index e20463634fcee9a9079d31ecca9284b151eb505c..5cd80fb35f3b63929f10b7f46a062afa46c8c792 100644
--- a/c++/src/capnproto/wire-format.h
+++ b/c++/src/capnproto/wire-format.h
@@ -240,7 +240,7 @@ public:
   // elementDefaultValue.  As with StructBuilder::initStructListElement(), this should be the
   // default value for the *type*, with all-null references.
 
-  ListBuilder getListElement(WireReferenceCount index, FieldSize elementSize) const;
+  ListBuilder getListElement(WireReferenceCount index) const;
   // Get the existing list element at the given index.  Returns an empty list if the element is
   // not initialized.
 
@@ -280,8 +280,7 @@ public:
   StructReader getStructElement(ElementCount index, const word* defaultValue) const;
   // Get the struct element at the given index.
 
-  ListReader getListElement(WireReferenceCount index, FieldSize expectedElementSize,
-                            const word* defaultValue) const;
+  ListReader getListElement(WireReferenceCount index, FieldSize expectedElementSize) const;
   // Get the list element at the given index.
 
 private:
diff --git a/compiler/src/CxxGenerator.hs b/compiler/src/CxxGenerator.hs
index 903854f2e995f0951297e7ad3e21b7f515ac19e9..64e7970557052cabae15e1f3497916d73770e668 100644
--- a/compiler/src/CxxGenerator.hs
+++ b/compiler/src/CxxGenerator.hs
@@ -61,30 +61,30 @@ isStruct _ = False
 isList (ListType _) = True
 isList _ = False
 
-isPrimitiveList (ListType t) = isPrimitive t
-isPrimitiveList _ = False
+isNonStructList (ListType t) = not $ isStruct t
+isNonStructList _ = False
 
 isStructList (ListType t) = isStruct t
 isStructList _ = False
 
 cxxTypeString (BuiltinType BuiltinVoid) = "void"
 cxxTypeString (BuiltinType BuiltinBool) = "bool"
-cxxTypeString (BuiltinType BuiltinInt8) = "int8_t"
-cxxTypeString (BuiltinType BuiltinInt16) = "int16_t"
-cxxTypeString (BuiltinType BuiltinInt32) = "int32_t"
-cxxTypeString (BuiltinType BuiltinInt64) = "int64_t"
-cxxTypeString (BuiltinType BuiltinUInt8) = "uint8_t"
-cxxTypeString (BuiltinType BuiltinUInt16) = "uint16_t"
-cxxTypeString (BuiltinType BuiltinUInt32) = "uint32_t"
-cxxTypeString (BuiltinType BuiltinUInt64) = "uint64_t"
+cxxTypeString (BuiltinType BuiltinInt8) = " ::int8_t"
+cxxTypeString (BuiltinType BuiltinInt16) = " ::int16_t"
+cxxTypeString (BuiltinType BuiltinInt32) = " ::int32_t"
+cxxTypeString (BuiltinType BuiltinInt64) = " ::int64_t"
+cxxTypeString (BuiltinType BuiltinUInt8) = " ::uint8_t"
+cxxTypeString (BuiltinType BuiltinUInt16) = " ::uint16_t"
+cxxTypeString (BuiltinType BuiltinUInt32) = " ::uint32_t"
+cxxTypeString (BuiltinType BuiltinUInt64) = " ::uint64_t"
 cxxTypeString (BuiltinType BuiltinFloat32) = "float"
 cxxTypeString (BuiltinType BuiltinFloat64) = "double"
 cxxTypeString (BuiltinType BuiltinText) = "TODO"
 cxxTypeString (BuiltinType BuiltinData) = "TODO"
-cxxTypeString (EnumType desc) = enumName desc
-cxxTypeString (StructType desc) = structName desc
-cxxTypeString (InterfaceType desc) = interfaceName desc
-cxxTypeString (ListType t) = concat ["::capnproto::List<", cxxTypeString t, ">"]
+cxxTypeString (EnumType desc) = enumName desc               -- TODO: full name
+cxxTypeString (StructType desc) = structName desc           -- TODO: full name
+cxxTypeString (InterfaceType desc) = interfaceName desc     -- TODO: full name
+cxxTypeString (ListType t) = concat [" ::capnproto::List<", cxxTypeString t, ">"]
 
 cxxFieldSizeString Size0 = "VOID";
 cxxFieldSizeString Size1 = "BIT";
@@ -161,7 +161,7 @@ fieldContext parent desc = mkStrContext context where
     context "fieldIsPrimitive" = MuBool $ isPrimitive $ fieldType desc
     context "fieldIsStruct" = MuBool $ isStruct $ fieldType desc
     context "fieldIsList" = MuBool $ isList $ fieldType desc
-    context "fieldIsPrimitiveList" = MuBool $ isPrimitiveList $ fieldType desc
+    context "fieldIsNonStructList" = MuBool $ isNonStructList $ fieldType desc
     context "fieldIsStructList" = MuBool $ isStructList $ fieldType desc
     context "fieldDefaultBytes" =
         case fieldDefaultValue desc >>= defaultValueBytes (fieldType desc) of
@@ -173,12 +173,16 @@ fieldContext parent desc = mkStrContext context where
         Just v -> MuVariable $ cxxValueString v
         Nothing -> MuVariable $ cxxDefaultDefault $ fieldType desc
     context "fieldElementSize" =
-        MuVariable $ cxxFieldSizeString $ fieldSize $ elementType $ fieldType desc
+        MuVariable $ cxxFieldSizeString $ elementSize $ elementType $ fieldType desc
+    context "fieldElementType" =
+        MuVariable $ cxxTypeString $ elementType $ fieldType desc
     context s = parent s
 
 structContext parent desc = mkStrContext context where
     context "structName" = MuVariable $ structName desc
     context "structFields" = MuList $ map (fieldContext context) $ structFields desc
+    context "structDataSize" = MuVariable $ packingDataSize $ structPacking desc
+    context "structReferenceCount" = MuVariable $ packingReferenceCount $ structPacking desc
     context "structChildren" = MuList []  -- TODO
     context "structDefault" = MuList [defaultBytesContext context
         (encodeMessage (StructType desc) (StructValueDesc []))]
@@ -191,7 +195,7 @@ fileContext desc = mkStrContext context where
         "CAPNPROTO_INCLUDED_" ++ hashString (fileName desc)
     context "fileNamespaces" = MuList []  -- TODO
     context "fileStructs" = MuList $ map (structContext context) $ fileStructs desc
-    context s = MuVariable $ concat ["@@@", s, "@@@"]
+    context s = error ("Template variable not defined: " ++ s)
 
 headerTemplate :: String
 headerTemplate = ByteStringUTF8.toString $(embedFile "src/c++-header.mustache")
diff --git a/compiler/src/c++-header.mustache b/compiler/src/c++-header.mustache
index 4488db06aee644506765ee852896241e73371907..f17d813627f2c8ca641abc4a30d81a03ce9aa458 100644
--- a/compiler/src/c++-header.mustache
+++ b/compiler/src/c++-header.mustache
@@ -41,6 +41,9 @@ struct {{structName}} {
   struct {{structChildName}};
 {{/structChildren}}
 
+  static constexpr ::capnproto::WordCount DATA_SIZE = {{structDataSize}} * ::capnproto::WORDS;
+  static constexpr ::capnproto::WireReferenceCount REFERENCE_COUNT =
+      {{structReferenceCount}} * ::capnproto::REFERENCES;
 {{#structDefault}}
   static const ::capnproto::internal::AlignedData<{{defaultWordCount}}> DEFAULT;
 {{/structDefault}}
@@ -91,10 +94,10 @@ public:
   inline {{fieldType}}::Builder init{{fieldTitleCase}}();
   inline {{fieldType}}::Builder get{{fieldTitleCase}}();
 {{/fieldIsStruct}}
-{{#fieldIsPrimitiveList}}
+{{#fieldIsNonStructList}}
   inline {{fieldType}}::Builder init{{fieldTitleCase}}(unsigned int size);
   inline {{fieldType}}::Builder get{{fieldTitleCase}}();
-{{/fieldIsPrimitiveList}}
+{{/fieldIsNonStructList}}
 {{#fieldIsStructList}}
   inline {{fieldType}}::Builder init{{fieldTitleCase}}(unsigned int size);
   inline {{fieldType}}::Builder get{{fieldTitleCase}}();
@@ -160,7 +163,7 @@ inline {{fieldType}}::Builder {{structName}}::Builder::get{{fieldTitleCase}}() {
       {{^fieldDefaultBytes}}{{fieldType}}::DEFAULT.words{{/fieldDefaultBytes}}));
 }
 {{/fieldIsStruct}}
-{{#fieldIsPrimitiveList}}
+{{#fieldIsNonStructList}}
 inline {{fieldType}}::Builder {{structName}}::Builder::init{{fieldTitleCase}}(unsigned int size) {
   return {{fieldType}}::Builder(_builder.initListField(
       {{fieldOffset}} * ::capnproto::REFERENCES,
@@ -173,12 +176,12 @@ inline {{fieldType}}::Builder {{structName}}::Builder::get{{fieldTitleCase}}() {
       {{#fieldDefaultBytes}}DEFAULT_{{fieldUpperCase}}.words{{/fieldDefaultBytes}}
       {{^fieldDefaultBytes}}nullptr{{/fieldDefaultBytes}}));
 }
-{{/fieldIsPrimitiveList}}
+{{/fieldIsNonStructList}}
 {{#fieldIsStructList}}
 inline {{fieldType}}::Builder {{structName}}::Builder::init{{fieldTitleCase}}(unsigned int size) {
   return {{fieldType}}::Builder(_builder.initStructListField(
       {{fieldOffset}} * ::capnproto::REFERENCES, size * ::capnproto::ELEMENTS,
-      {{fieldType}}::DEFAULT.words));
+      {{fieldElementType}}::DEFAULT.words));
 }
 inline {{fieldType}}::Builder {{structName}}::Builder::get{{fieldTitleCase}}() {
   return {{fieldType}}::Builder(_builder.getListField(
diff --git a/compiler/src/c++-source.mustache b/compiler/src/c++-source.mustache
index ebb9a851edadb1b8f1a4379fe9285e92caf4ec9f..ca0c85bf7f20d69e6aba040ea53bd43e06aea9d1 100644
--- a/compiler/src/c++-source.mustache
+++ b/compiler/src/c++-source.mustache
@@ -32,6 +32,8 @@ namespace {{namespaceName}} {
 {{/fileNamespaces}}
 
 {{#fileStructs}}
+constexpr ::capnproto::WordCount {{structName}}::DATA_SIZE;
+constexpr ::capnproto::WireReferenceCount {{structName}}::REFERENCE_COUNT;
 {{#structDefault}}
 const ::capnproto::internal::AlignedData<{{defaultWordCount}}> {{structName}}::DEFAULT = {
   { {{defaultByteList}} }