From 63b6b5226926eeca73700c581a8fc7ffe2d0ad11 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 19:09:55 -0700 Subject: [PATCH 01/26] Support deserializing into temporaries Currently, the READWRITE macro cannot be passed any non-const temporaries, as the SerReadWrite function only accepts lvalue references. Deserializing into a temporary is very common, however. See for example things like 's >> VARINT(n)'. The VARINT macro produces a temporary wrapper that holds a reference to n. Fix this by accepting non-const rvalue references instead of lvalue references. We don't propagate the rvalue-ness down, as there are no useful optimizations that only apply to temporaries. --- src/hash.h | 2 +- src/script/bitcoinconsensus.cpp | 2 +- src/serialize.h | 24 ++++++++++++------------ src/streams.h | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/hash.h b/src/hash.h index b9952d39fc9..f9b3fa2ca18 100644 --- a/src/hash.h +++ b/src/hash.h @@ -187,7 +187,7 @@ class CHashVerifier : public CHashWriter } template - CHashVerifier& operator>>(T& obj) + CHashVerifier& operator>>(T&& obj) { // Unserialize from this stream ::Unserialize(*this, obj); diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp index 03128917fd8..003ed5d0df0 100644 --- a/src/script/bitcoinconsensus.cpp +++ b/src/script/bitcoinconsensus.cpp @@ -40,7 +40,7 @@ class TxInputStream } template - TxInputStream& operator>>(T& obj) + TxInputStream& operator>>(T&& obj) { ::Unserialize(*this, obj); return *this; diff --git a/src/serialize.h b/src/serialize.h index 8b86a07a767..8c7702e84e1 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -539,7 +539,7 @@ inline void Serialize(Stream& os, const T& a) } template -inline void Unserialize(Stream& is, T& a) +inline void Unserialize(Stream& is, T&& a) { a.Unserialize(is); } @@ -832,7 +832,7 @@ inline void SerReadWrite(Stream& s, const T& obj, CSerActionSerialize ser_action } template -inline void SerReadWrite(Stream& s, T& obj, CSerActionUnserialize ser_action) +inline void SerReadWrite(Stream& s, T&& obj, CSerActionUnserialize ser_action) { ::Unserialize(s, obj); } @@ -898,16 +898,16 @@ void SerializeMany(Stream& s) } template -void SerializeMany(Stream& s, Arg&& arg) +void SerializeMany(Stream& s, const Arg& arg) { - ::Serialize(s, std::forward(arg)); + ::Serialize(s, arg); } template -void SerializeMany(Stream& s, Arg&& arg, Args&&... args) +void SerializeMany(Stream& s, const Arg& arg, const Args&... args) { - ::Serialize(s, std::forward(arg)); - ::SerializeMany(s, std::forward(args)...); + ::Serialize(s, arg); + ::SerializeMany(s, args...); } template @@ -916,26 +916,26 @@ inline void UnserializeMany(Stream& s) } template -inline void UnserializeMany(Stream& s, Arg& arg) +inline void UnserializeMany(Stream& s, Arg&& arg) { ::Unserialize(s, arg); } template -inline void UnserializeMany(Stream& s, Arg& arg, Args&... args) +inline void UnserializeMany(Stream& s, Arg&& arg, Args&&... args) { ::Unserialize(s, arg); ::UnserializeMany(s, args...); } template -inline void SerReadWriteMany(Stream& s, CSerActionSerialize ser_action, Args&&... args) +inline void SerReadWriteMany(Stream& s, CSerActionSerialize ser_action, const Args&... args) { - ::SerializeMany(s, std::forward(args)...); + ::SerializeMany(s, args...); } template -inline void SerReadWriteMany(Stream& s, CSerActionUnserialize ser_action, Args&... args) +inline void SerReadWriteMany(Stream& s, CSerActionUnserialize ser_action, Args&&... args) { ::UnserializeMany(s, args...); } diff --git a/src/streams.h b/src/streams.h index a9668b68bcb..81267792e83 100644 --- a/src/streams.h +++ b/src/streams.h @@ -42,7 +42,7 @@ class OverrideStream } template - OverrideStream& operator>>(T& obj) + OverrideStream& operator>>(T&& obj) { // Unserialize from this stream ::Unserialize(*this, obj); @@ -401,7 +401,7 @@ class CDataStream } template - CDataStream& operator>>(T& obj) + CDataStream& operator>>(T&& obj) { // Unserialize from this stream ::Unserialize(*this, obj); @@ -545,7 +545,7 @@ class CAutoFile } template - CAutoFile& operator>>(T& obj) + CAutoFile& operator>>(T&& obj) { // Unserialize from this stream if (!file) @@ -688,7 +688,7 @@ class CBufferedFile } template - CBufferedFile& operator>>(T& obj) { + CBufferedFile& operator>>(T&& obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); From 6639b1865f2a1bc2b22b75c3135f076469d47bdf Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 15:48:13 -0700 Subject: [PATCH 02/26] Introduce new serialization macros without casts This new approach uses a static method which takes the object as a argument. This has the advantage that its constness can be a template parameter, allowing a single implementation that sees the object as const for serialization and non-const for deserialization, without casts. More boilerplate is included in the new macro as well. --- src/serialize.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/serialize.h b/src/serialize.h index 8c7702e84e1..278f08492c5 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -167,6 +167,26 @@ enum SerializationOp(s, CSerActionUnserialize()); \ } +/** + * Implement the Serialize and Unserialize methods by delegating to a single templated + * static method that takes the to-be-(de)serialized object as a parameter. This approach + * has the advantage that the constness of the object becomes a template parameter, and + * thus allows a single implementation that sees the object as const for serializing + * and non-const for deserializing, without casts. + */ +#define SERIALIZE_METHODS(obj) \ + template \ + void Serialize(Stream& s) const { \ + SerializationOps(*this, s, CSerActionSerialize()); \ + } \ + template \ + void Unserialize(Stream& s) { \ + SerializationOps(*this, s, CSerActionUnserialize()); \ + } \ + template \ + static inline void SerializationOps(Type& obj, Stream& s, Operation ser_action) \ + + template inline void Serialize(Stream& s, char a ) { ser_writedata8(s, a); } // TODO Get rid of bare char template inline void Serialize(Stream& s, int8_t a ) { ser_writedata8(s, a); } template inline void Serialize(Stream& s, uint8_t a ) { ser_writedata8(s, a); } From 11811b7d12ed6ddb97ab6c2de372f648e506be85 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 16:06:56 -0700 Subject: [PATCH 03/26] Add READWRITEAS, a macro to serialize safely as a different type --- src/serialize.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/serialize.h b/src/serialize.h index 278f08492c5..b7a9a831cdb 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -148,9 +148,21 @@ enum SER_GETHASH = (1 << 2), }; +// Convert the reference base type to X, without changing constness or reference type. +template X& AsBaseType(X& x) { return x; } +template const X& AsBaseType(const X& x) { return x; } +template X&& AsBaseType(X&& x) { return std::move(x); } +template const X&& AsBaseType(const X&& x) { return std::move(x); } + #define READWRITE(obj) (::SerReadWrite(s, (obj), ser_action)) #define READWRITEMANY(...) (::SerReadWriteMany(s, ser_action, __VA_ARGS__)) +/** Serialize/deserialize the given object after converting it to a reference of + * type `typ`, while preserving reference type and constness. Use this instead + * of casting manually, as it will not inadvertently remove constness. + */ +#define READWRITEAS(obj, typ) (::SerReadWrite(s, AsBaseType(obj), ser_action)) + /** * Implement three methods for serializable objects. These are actually wrappers over * "SerializationOp" template, which implements the body of each class' serialization From 2fd24cfa9f5a75fcf3611914bce080f23ebf64ae Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jul 2017 13:11:38 -0700 Subject: [PATCH 04/26] Merge READWRITEMANY into READWRITE READWRITEMANY is more general, and its single-argument form is identical to READWRITE. After this, only a variable-argument READWRITE remains. --- src/serialize.h | 17 ++--------------- src/test/serialize_tests.cpp | 2 +- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index b7a9a831cdb..549b33b5b41 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -154,14 +154,13 @@ template const X& AsBaseType(const X& x) { return x; } template X&& AsBaseType(X&& x) { return std::move(x); } template const X&& AsBaseType(const X&& x) { return std::move(x); } -#define READWRITE(obj) (::SerReadWrite(s, (obj), ser_action)) -#define READWRITEMANY(...) (::SerReadWriteMany(s, ser_action, __VA_ARGS__)) +#define READWRITE(...) (::SerReadWriteMany(s, ser_action, __VA_ARGS__)) /** Serialize/deserialize the given object after converting it to a reference of * type `typ`, while preserving reference type and constness. Use this instead * of casting manually, as it will not inadvertently remove constness. */ -#define READWRITEAS(obj, typ) (::SerReadWrite(s, AsBaseType(obj), ser_action)) +#define READWRITEAS(obj, typ) (::SerReadWriteMany(s, ser_action, AsBaseType(obj))) /** * Implement three methods for serializable objects. These are actually wrappers over @@ -857,18 +856,6 @@ struct CSerActionUnserialize constexpr bool ForRead() const { return true; } }; -template -inline void SerReadWrite(Stream& s, const T& obj, CSerActionSerialize ser_action) -{ - ::Serialize(s, obj); -} - -template -inline void SerReadWrite(Stream& s, T&& obj, CSerActionUnserialize ser_action) -{ - ::Unserialize(s, obj); -} - diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index 9661a665140..36af6de88ea 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -53,7 +53,7 @@ class CSerializeMethodsTestMany : public CSerializeMethodsTestSingle template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITEMANY(intval, boolval, stringval, FLATDATA(charstrval), txval); + READWRITE(intval, boolval, stringval, FLATDATA(charstrval), txval); } }; From c38cfd6747d351c540ab71878c5640663921baaa Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 8 Jul 2017 13:31:34 -0700 Subject: [PATCH 05/26] Add BigEndian serialization wrapper --- src/serialize.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/serialize.h b/src/serialize.h index 549b33b5b41..c249fe8952a 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -72,6 +72,11 @@ template inline void ser_writedata16(Stream &s, uint16_t obj) obj = htole16(obj); s.write((char*)&obj, 2); } +template inline void ser_writedata16be(Stream &s, uint16_t obj) +{ + obj = htobe16(obj); + s.write((char*)&obj, 2); +} template inline void ser_writedata32(Stream &s, uint32_t obj) { obj = htole32(obj); @@ -94,6 +99,12 @@ template inline uint16_t ser_readdata16(Stream &s) s.read((char*)&obj, 2); return le16toh(obj); } +template inline uint16_t ser_readdata16be(Stream &s) +{ + uint16_t obj; + s.read((char*)&obj, 2); + return be16toh(obj); +} template inline uint32_t ser_readdata32(Stream &s) { uint32_t obj; @@ -493,9 +504,42 @@ class LimitedString } }; +/** Serialization wrapper class for big-endian integers. + * + * Use this wrapper around integer types that are stored in memory in native + * byte order, but serialized in big endian notation. + * + * Onlyy 16-bit types are supported for now. + */ +template class BigEndianWrapper +{ +protected: + I& m_val; +public: + BigEndianWrapper(I& val) : m_val(val) + { + static_assert(S == 2, "Unsupported BigEndian size"); + } + + template + void Serialize(Stream& s) const + { + if (S == 2) ser_writedata16be(s, m_val); + } + + template + void Unserialize(Stream& s) + { + if (S == 2) m_val = ser_readdata16be(s); + } +}; +//! Automatically construct a BigEndianWrapper around the argument. +template static inline BigEndianWrapper BigEndian(I& i) { return BigEndianWrapper(i); } + template CVarInt WrapVarInt(I& n) { return CVarInt(n); } + /** * Forward declarations */ From 3f71927fa06a7b1cbbd9c245adc78a7ab86abdfa Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 17:12:06 -0700 Subject: [PATCH 06/26] Generalize CompactSize wrapper This makes it const-correct and usable for other integer types. --- src/serialize.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index c249fe8952a..d22fa870a96 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -395,7 +395,6 @@ I ReadVarInt(Stream& is) #define FLATDATA(obj) REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))) #define VARINT(obj) REF(WrapVarInt(REF(obj))) -#define COMPACTSIZE(obj) REF(CCompactSize(REF(obj))) #define LIMITED_STRING(obj,n) REF(LimitedString< n >(REF(obj))) /** @@ -457,23 +456,27 @@ class CVarInt } }; -class CCompactSize +/** Serialization wrapper class for integers in CompactSize format. */ +template +class CompactSizeWrapper { protected: - uint64_t &n; + I &n; public: - CCompactSize(uint64_t& nIn) : n(nIn) { } + CompactSizeWrapper(I& nIn) : n(nIn) { } template - void Serialize(Stream &s) const { - WriteCompactSize(s, n); + void Unserialize(Stream& s) { + n = ReadCompactSize(s); } template - void Unserialize(Stream& s) { - n = ReadCompactSize(s); + void Serialize(Stream& s) const { + WriteCompactSize(s, n); } }; +//! Automatically construct a CompactSize wrapper around the argument. +template static inline CompactSizeWrapper COMPACTSIZE(I& i) { return CompactSizeWrapper(i); } template class LimitedString @@ -539,7 +542,6 @@ template static inline BigEndianWrapper BigEndian(I& i) template CVarInt WrapVarInt(I& n) { return CVarInt(n); } - /** * Forward declarations */ From 7e7a055fccd8964c5373b907ae1a929377ac3c24 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 17:15:56 -0700 Subject: [PATCH 07/26] Generalize VarInt wrappers --- src/serialize.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index d22fa870a96..54785feac73 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -394,7 +394,6 @@ I ReadVarInt(Stream& is) } #define FLATDATA(obj) REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))) -#define VARINT(obj) REF(WrapVarInt(REF(obj))) #define LIMITED_STRING(obj,n) REF(LimitedString< n >(REF(obj))) /** @@ -437,17 +436,18 @@ class CFlatData } }; +/** Serialization wrapper class for integers in VarInt format. */ template -class CVarInt +class VarIntWrapper { protected: I &n; public: - CVarInt(I& nIn) : n(nIn) { } + VarIntWrapper(I& nIn) : n(nIn) { } template void Serialize(Stream &s) const { - WriteVarInt(s, n); + WriteVarInt(s, n); } template @@ -455,6 +455,8 @@ class CVarInt n = ReadVarInt(s); } }; +//! Automatically construct a VarInt wrapper around the argument. +template static inline VarIntWrapper::type> VARINT(I&& i) { return VarIntWrapper::type>(i); } /** Serialization wrapper class for integers in CompactSize format. */ template @@ -539,9 +541,6 @@ template class BigEndianWrapper //! Automatically construct a BigEndianWrapper around the argument. template static inline BigEndianWrapper BigEndian(I& i) { return BigEndianWrapper(i); } -template -CVarInt WrapVarInt(I& n) { return CVarInt(n); } - /** * Forward declarations */ From 4b9d7b1954f3b90202522102161e9f3ca7e9d7e8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 17:28:47 -0700 Subject: [PATCH 08/26] Generalize FlatData wrapper --- src/compressor.h | 8 ++++---- src/serialize.h | 40 ++++++++++++++++------------------------ 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/compressor.h b/src/compressor.h index 015911484a1..95699850817 100644 --- a/src/compressor.h +++ b/src/compressor.h @@ -59,12 +59,12 @@ class CScriptCompressor void Serialize(Stream &s) const { std::vector compr; if (Compress(compr)) { - s << CFlatData(compr); + s << FlatVector(compr); return; } unsigned int nSize = script.size() + nSpecialScripts; s << VARINT(nSize); - s << CFlatData(script); + s << FlatVector(script); } template @@ -73,7 +73,7 @@ class CScriptCompressor s >> VARINT(nSize); if (nSize < nSpecialScripts) { std::vector vch(GetSpecialSize(nSize), 0x00); - s >> REF(CFlatData(vch)); + s >> FlatVector(vch); Decompress(nSize, vch); return; } @@ -84,7 +84,7 @@ class CScriptCompressor s.ignore(nSize); } else { script.resize(nSize); - s >> REF(CFlatData(script)); + s >> FlatVector(script); } } }; diff --git a/src/serialize.h b/src/serialize.h index 54785feac73..4588c0b4ee9 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -393,35 +393,17 @@ I ReadVarInt(Stream& is) } } -#define FLATDATA(obj) REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))) #define LIMITED_STRING(obj,n) REF(LimitedString< n >(REF(obj))) -/** - * Wrapper for serializing arrays and POD. - */ -class CFlatData +/** Wrapper for serializing arrays and POD. */ +template +class FlatRangeWrapper { protected: - char* pbegin; - char* pend; + C* pbegin; + C* pend; public: - CFlatData(void* pbeginIn, void* pendIn) : pbegin((char*)pbeginIn), pend((char*)pendIn) { } - template - explicit CFlatData(std::vector &v) - { - pbegin = (char*)v.data(); - pend = (char*)(v.data() + v.size()); - } - template - explicit CFlatData(prevector &v) - { - pbegin = (char*)v.data(); - pend = (char*)(v.data() + v.size()); - } - char* begin() { return pbegin; } - const char* begin() const { return pbegin; } - char* end() { return pend; } - const char* end() const { return pend; } + FlatRangeWrapper(C* pbeginIn, C* pendIn) : pbegin(pbeginIn), pend(pendIn) { } template void Serialize(Stream& s) const @@ -435,6 +417,16 @@ class CFlatData s.read(pbegin, pend - pbegin); } }; +//! Construct a FlatRange wrapper around a const vector. +template static inline const FlatRangeWrapper FlatVector(const T& t) { return FlatRangeWrapper((const char*)t.data(), (const char*)(t.data() + t.size())); } +//! Construct a FlatRange wrapper around a non-const vector. +template static inline FlatRangeWrapper FlatVector(T& t) { return FlatRangeWrapper((char*)t.data(), (char*)(t.data() + t.size())); } +//! Construct a FlatRange wrapper around a const POD and array types. +template static inline const FlatRangeWrapper FlatDataInner(const T* t, size_t len) { return FlatRangeWrapper((const char*)t, ((const char*)t) + len); } +//! Construct a FlatRange wrapper around a non-const POD and array types. +template static inline FlatRangeWrapper FlatDataInner(T* t, size_t len) { return FlatRangeWrapper((char*)t, ((char*)t) + len); } +//! Helper macro to easily serialize POD types. +#define FLATDATA(x) FlatDataInner(&(x), sizeof(x)) /** Serialization wrapper class for integers in VarInt format. */ template From 644e8c2c56588f2f83632fc1eff6b9583c7bade8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 7 Jul 2017 17:35:27 -0700 Subject: [PATCH 09/26] Generalize LimitedString wrapper --- src/serialize.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index 4588c0b4ee9..7786fd99e82 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -393,8 +393,6 @@ I ReadVarInt(Stream& is) } } -#define LIMITED_STRING(obj,n) REF(LimitedString< n >(REF(obj))) - /** Wrapper for serializing arrays and POD. */ template class FlatRangeWrapper @@ -472,13 +470,14 @@ class CompactSizeWrapper //! Automatically construct a CompactSize wrapper around the argument. template static inline CompactSizeWrapper COMPACTSIZE(I& i) { return CompactSizeWrapper(i); } +/** Serialization wrapper class for strings of limited length. */ template -class LimitedString +class LimitedStringWrapper { protected: std::string& string; public: - LimitedString(std::string& _string) : string(_string) {} + LimitedStringWrapper(std::string& _string) : string(_string) {} template void Unserialize(Stream& s) @@ -495,11 +494,14 @@ class LimitedString template void Serialize(Stream& s) const { - WriteCompactSize(s, string.size()); - if (!string.empty()) - s.write((char*)string.data(), string.size()); + s << string; } }; +//! Add a LimitedString wrapper around a const string (identity) +template static inline const std::string& LimitedString(const std::string& str) { return str; } +//! Add a LimitedString wrapper around a non-const string +template static inline LimitedStringWrapper LimitedString(std::string& str) { return LimitedStringWrapper(str); } +#define LIMITED_STRING(obj, n) LimitedString(obj) /** Serialization wrapper class for big-endian integers. * @@ -533,6 +535,7 @@ template class BigEndianWrapper //! Automatically construct a BigEndianWrapper around the argument. template static inline BigEndianWrapper BigEndian(I& i) { return BigEndianWrapper(i); } + /** * Forward declarations */ From 8db0bfe88496afa9c48502532d66b5edb1177327 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 9 Jul 2017 22:45:24 -0700 Subject: [PATCH 10/26] Add custom vector-element serialization wrapper This allows a very compact notation for serialization of vectors whose elements are not serialized using their default encoding. --- src/serialize.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/serialize.h b/src/serialize.h index 7786fd99e82..dcf1b5390ef 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -535,6 +535,64 @@ template class BigEndianWrapper //! Automatically construct a BigEndianWrapper around the argument. template static inline BigEndianWrapper BigEndian(I& i) { return BigEndianWrapper(i); } +/** Serialization wrapper for custom-element vectors. + * + * This allows (de)serialization of vectors of type V while using a custom + * serializer W for the entries inside. + * + * For const-correctness, the W parameter is a template itself. + * It is instantiated as W when serializing, and as + * W when deserializing. + * + * Example: + * struct X { + * std::vector v; + * SERIALIZE_METHODS(obj) { READWRITE(VectorApply(obj.v)); } + * }; + * will define a struct that contains a vector of uint64_t, which is serialized + * as a vector of VarInt-encoded integers. + * + * V is not required to be an std::vector type. It works for any class that + * exposes a value_type, iteration, and resize method that behave like vectors. + */ +template