diff --git a/atdna/main.cpp b/atdna/main.cpp index df27aa2..114f07c 100644 --- a/atdna/main.cpp +++ b/atdna/main.cpp @@ -21,6 +21,7 @@ static unsigned AthenaError = 0; #define ATHENA_DNA_WRITER "__dna_writer" #define ATHENA_YAML_READER "__dna_docin" #define ATHENA_YAML_WRITER "__dna_docout" +#define ATHENA_SZ_ENUMERATE "__EnumerateSize" #ifndef INSTALL_PREFIX #define INSTALL_PREFIX /usr/local @@ -130,6 +131,70 @@ class ATDNAEmitVisitor : public clang::RecursiveASTVisitor return false; } + int64_t GetSizeValue(const clang::Type* theType, unsigned width) + { + if (theType->isEnumeralType()) + { + clang::EnumType* eType = (clang::EnumType*)theType; + clang::EnumDecl* eDecl = eType->getDecl(); + theType = eDecl->getIntegerType().getCanonicalType().getTypePtr(); + + const clang::BuiltinType* bType = (clang::BuiltinType*)theType; + if (bType->isBooleanType()) + { + return 1; + } + else if (bType->isUnsignedInteger() || bType->isSignedInteger()) + { + return width / 8; + } + } + else if (theType->isBuiltinType()) + { + const clang::BuiltinType* bType = (clang::BuiltinType*)theType; + if (bType->isBooleanType()) + { + return 1; + } + else if (bType->isUnsignedInteger() || bType->isSignedInteger() || bType->isFloatingPoint()) + { + return width / 8; + } + } + else if (theType->isRecordType()) + { + const clang::CXXRecordDecl* rDecl = theType->getAsCXXRecordDecl(); + for (const clang::FieldDecl* field : rDecl->fields()) + { + if (!field->getName().compare("clangVec")) + { + const clang::VectorType* vType = (clang::VectorType*)field->getType().getTypePtr(); + if (vType->isVectorType()) + { + const clang::BuiltinType* eType = (clang::BuiltinType*)vType->getElementType().getTypePtr(); + const uint64_t width = context.getTypeInfo(eType).Width; + if (!eType->isBuiltinType() || !eType->isFloatingPoint() || + (width != 32 && width != 64)) + continue; + if (vType->getNumElements() == 2) + { + return width / 8 * 2; + } + else if (vType->getNumElements() == 3) + { + return width / 8 * 3; + } + else if (vType->getNumElements() == 4) + { + return width / 8 * 4; + } + } + } + } + } + return 0; + } + std::string GetOpString(const clang::Type* theType, unsigned width, const std::string& fieldName, bool writerPass, const std::string& funcPrefix, bool& isDNATypeOut) @@ -630,6 +695,356 @@ class ATDNAEmitVisitor : public clang::RecursiveASTVisitor return std::string(); } + void emitSizeFuncs(clang::CXXRecordDecl* decl, const std::string& baseDNA) + { + int64_t podTotal = 0; + fileOut << "size_t " << decl->getQualifiedNameAsString() << "::binarySize(size_t __isz) const\n{\n"; + + if (baseDNA.size()) + { + fileOut << " __isz = " << baseDNA << "::binarySize(__isz);\n"; + } + + for (const clang::FieldDecl* field : decl->fields()) + { + clang::QualType qualType = field->getType(); + clang::TypeInfo regTypeInfo = context.getTypeInfo(qualType); + const clang::Type* regType = qualType.getTypePtrOrNull(); + while (regType->getTypeClass() == clang::Type::Elaborated || + regType->getTypeClass() == clang::Type::Typedef) + regType = regType->getUnqualifiedDesugaredType(); + + /* Resolve constant array */ + size_t arraySize = 1; + bool isArray = false; + if (regType->getTypeClass() == clang::Type::ConstantArray) + { + isArray = true; + const clang::ConstantArrayType* caType = (clang::ConstantArrayType*)regType; + arraySize = caType->getSize().getZExtValue(); + qualType = caType->getElementType(); + regTypeInfo = context.getTypeInfo(qualType); + regType = qualType.getTypePtrOrNull(); + if (regType->getTypeClass() == clang::Type::Elaborated) + regType = regType->getUnqualifiedDesugaredType(); + } + + for (int e=0 ; egetName().str() + subscript; + } + else + fieldName = field->getName(); + + if (regType->getTypeClass() == clang::Type::TemplateSpecialization) + { + const clang::TemplateSpecializationType* tsType = (const clang::TemplateSpecializationType*)regType; + const clang::TemplateDecl* tsDecl = tsType->getTemplateName().getAsTemplateDecl(); + + if (!tsDecl->getName().compare("Value")) + { + clang::QualType templateType; + const clang::TemplateArgument* typeArg = nullptr; + size_t typeSize = 0; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Type) + { + typeArg = &arg; + templateType = arg.getAsType().getCanonicalType(); + const clang::Type* type = arg.getAsType().getCanonicalType().getTypePtr(); + typeSize = GetSizeValue(type, regTypeInfo.Width); + } + } + + if (typeSize) + podTotal += typeSize; + else + fileOut << " __isz = " << fieldName << ".binarySize(__isz);\n"; + } + else if (!tsDecl->getName().compare("Vector")) + { + clang::QualType templateType; + const clang::TemplateArgument* typeArg = nullptr; + size_t typeSize = 0; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Type) + { + typeArg = &arg; + templateType = arg.getAsType().getCanonicalType(); + clang::TypeInfo typeInfo = context.getTypeInfo(templateType); + typeSize = GetSizeValue(templateType.getTypePtr(), typeInfo.Width); + } + } + + if (typeSize) + fileOut << " __isz += " << fieldName << ".size() * " << typeSize << ";\n"; + else + fileOut << " __isz = " ATHENA_SZ_ENUMERATE "(__isz, " << fieldName << ");\n"; + + } + else if (!tsDecl->getName().compare("Buffer")) + { + const clang::Expr* sizeExpr = nullptr; + std::string sizeExprStr; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Expression) + { + const clang::UnaryExprOrTypeTraitExpr* uExpr = (clang::UnaryExprOrTypeTraitExpr*)arg.getAsExpr()->IgnoreImpCasts(); + if (uExpr->getStmtClass() == clang::Stmt::UnaryExprOrTypeTraitExprClass && + uExpr->getKind() == clang::UETT_SizeOf) + { + const clang::Expr* argExpr = uExpr->getArgumentExpr(); + while (argExpr->getStmtClass() == clang::Stmt::ParenExprClass) + argExpr = ((clang::ParenExpr*)argExpr)->getSubExpr(); + sizeExpr = argExpr; + llvm::raw_string_ostream strStream(sizeExprStr); + argExpr->printPretty(strStream, nullptr, context.getPrintingPolicy()); + } + } + } + + fileOut << " __isz += (" << sizeExprStr << ");\n"; + } + else if (!tsDecl->getName().compare("String")) + { + const clang::Expr* sizeExpr = nullptr; + std::string sizeExprStr; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Expression) + { + const clang::Expr* expr = arg.getAsExpr()->IgnoreImpCasts(); + const clang::UnaryExprOrTypeTraitExpr* uExpr = (clang::UnaryExprOrTypeTraitExpr*)expr; + llvm::APSInt sizeLiteral; + if (expr->getStmtClass() == clang::Stmt::UnaryExprOrTypeTraitExprClass && + uExpr->getKind() == clang::UETT_SizeOf) + { + const clang::Expr* argExpr = uExpr->getArgumentExpr(); + while (argExpr->getStmtClass() == clang::Stmt::ParenExprClass) + argExpr = ((clang::ParenExpr*)argExpr)->getSubExpr(); + sizeExpr = argExpr; + llvm::raw_string_ostream strStream(sizeExprStr); + argExpr->printPretty(strStream, nullptr, context.getPrintingPolicy()); + } + else if (expr->isIntegerConstantExpr(sizeLiteral, context)) + { + sizeExprStr = sizeLiteral.toString(10); + } + } + } + + if (sizeExprStr.size()) + fileOut << " __isz += (" << sizeExprStr << ");\n"; + else + fileOut << " __isz += " << fieldName << ".size() + 1;\n"; + } + else if (!tsDecl->getName().compare("WString")) + { + const clang::Expr* sizeExpr = nullptr; + std::string sizeExprStr; + size_t idx = 0; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Expression) + { + const clang::Expr* expr = arg.getAsExpr()->IgnoreImpCasts(); + if (idx == 0) + { + llvm::APSInt sizeLiteral; + const clang::UnaryExprOrTypeTraitExpr* uExpr = (clang::UnaryExprOrTypeTraitExpr*)expr; + if (expr->getStmtClass() == clang::Stmt::UnaryExprOrTypeTraitExprClass && + uExpr->getKind() == clang::UETT_SizeOf) + { + const clang::Expr* argExpr = uExpr->getArgumentExpr(); + while (argExpr->getStmtClass() == clang::Stmt::ParenExprClass) + argExpr = ((clang::ParenExpr*)argExpr)->getSubExpr(); + sizeExpr = argExpr; + llvm::raw_string_ostream strStream(sizeExprStr); + argExpr->printPretty(strStream, nullptr, context.getPrintingPolicy()); + } + else if (expr->isIntegerConstantExpr(sizeLiteral, context)) + { + sizeExprStr = sizeLiteral.toString(10); + } + } + } + ++idx; + } + + if (sizeExprStr.size()) + fileOut << " __isz += (" << sizeExprStr << ") * 2;\n"; + else + fileOut << " __isz += (" << fieldName << ".size() + 1) * 2;\n"; + } + else if (!tsDecl->getName().compare("WStringAsString")) + { + const clang::Expr* sizeExpr = nullptr; + std::string sizeExprStr; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Expression) + { + const clang::Expr* expr = arg.getAsExpr()->IgnoreImpCasts(); + const clang::UnaryExprOrTypeTraitExpr* uExpr = (clang::UnaryExprOrTypeTraitExpr*)expr; + llvm::APSInt sizeLiteral; + if (expr->getStmtClass() == clang::Stmt::UnaryExprOrTypeTraitExprClass && + uExpr->getKind() == clang::UETT_SizeOf) + { + const clang::Expr* argExpr = uExpr->getArgumentExpr(); + while (argExpr->getStmtClass() == clang::Stmt::ParenExprClass) + argExpr = ((clang::ParenExpr*)argExpr)->getSubExpr(); + sizeExpr = argExpr; + llvm::raw_string_ostream strStream(sizeExprStr); + argExpr->printPretty(strStream, nullptr, context.getPrintingPolicy()); + } + else if (expr->isIntegerConstantExpr(sizeLiteral, context)) + { + sizeExprStr = sizeLiteral.toString(10); + } + } + } + + + if (sizeExprStr.size()) + fileOut << " __isz += (" << sizeExprStr << ") * 2;\n"; + else + fileOut << " __isz += (" << fieldName << ".size() + 1) * 2;\n"; + } + else if (!tsDecl->getName().compare("Seek")) + { + size_t idx = 0; + const clang::Expr* offsetExpr = nullptr; + std::string offsetExprStr; + llvm::APSInt direction(64, 0); + const clang::Expr* directionExpr = nullptr; + bool bad = false; + int64_t literal = 0; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Expression) + { + const clang::Expr* expr = arg.getAsExpr()->IgnoreImpCasts(); + if (!idx) + { + offsetExpr = expr; + const clang::UnaryExprOrTypeTraitExpr* uExpr = (clang::UnaryExprOrTypeTraitExpr*)expr; + llvm::APSInt offsetLiteral; + if (expr->getStmtClass() == clang::Stmt::UnaryExprOrTypeTraitExprClass && + uExpr->getKind() == clang::UETT_SizeOf) + { + const clang::Expr* argExpr = uExpr->getArgumentExpr(); + while (argExpr->getStmtClass() == clang::Stmt::ParenExprClass) + argExpr = ((clang::ParenExpr*)argExpr)->getSubExpr(); + offsetExpr = argExpr; + llvm::raw_string_ostream strStream(offsetExprStr); + argExpr->printPretty(strStream, nullptr, context.getPrintingPolicy()); + } + else if (expr->isIntegerConstantExpr(offsetLiteral, context)) + { + literal = offsetLiteral.getSExtValue(); + } + } + else + { + directionExpr = expr; + if (!expr->isIntegerConstantExpr(direction, context)) + { + bad = true; + break; + } + } + } + ++idx; + } + if (bad) + continue; + + int64_t directionVal = direction.getSExtValue(); + + if (literal) + { + if (directionVal == 0) + { + podTotal = 0; + fileOut << " __isz = " << literal << ";\n"; + } + else if (directionVal == 1) + podTotal += literal; + } + else + { + if (directionVal == 0) + { + podTotal = 0; + fileOut << " __isz = (" << offsetExprStr << ");\n"; + } + else if (directionVal == 1) + fileOut << " __isz += (" << offsetExprStr << ");\n"; + } + + } + else if (!tsDecl->getName().compare("Align")) + { + llvm::APSInt align(64, 0); + bool bad = false; + for (const clang::TemplateArgument& arg : *tsType) + { + if (arg.getKind() == clang::TemplateArgument::Expression) + { + const clang::Expr* expr = arg.getAsExpr(); + if (!expr->isIntegerConstantExpr(align, context)) + { + bad = true; + break; + } + } + } + if (bad) + continue; + + int64_t alignVal = align.getSExtValue(); + if (alignVal) + { + fileOut << " __isz += " << podTotal << ";\n"; + podTotal = 0; + if (align.isPowerOf2()) + fileOut << " __isz = (__isz + " << alignVal-1 << ") & ~" << alignVal-1 << ";\n"; + else + fileOut << " __isz = (__isz + " << alignVal-1 << ") / " << alignVal << " * " << alignVal << ";\n"; + } + } + + } + + else if (regType->getTypeClass() == clang::Type::Record) + { + const clang::CXXRecordDecl* cxxRDecl = regType->getAsCXXRecordDecl(); + std::string baseDNA; + bool isYAML = false; + if (cxxRDecl && isDNARecord(cxxRDecl, baseDNA, isYAML)) + { + fileOut << " __isz = " << fieldName << ".binarySize(__isz);\n"; + } + } + + } + + } + + if (podTotal) + fileOut << " return __isz + " << podTotal << ";\n}\n\n"; + else + fileOut << " return __isz;\n}\n\n"; + } + void emitIOFuncs(clang::CXXRecordDecl* decl, const std::string& baseDNA) { /* Two passes - read then write */ @@ -1789,6 +2204,7 @@ public: emitIOFuncs(decl, baseDNA); if (isYAML) emitYAMLFuncs(decl, baseDNA); + emitSizeFuncs(decl, baseDNA); return true; } diff --git a/include/Athena/DNA.hpp b/include/Athena/DNA.hpp index 85dd91f..ea9c88b 100644 --- a/include/Athena/DNA.hpp +++ b/include/Athena/DNA.hpp @@ -44,6 +44,8 @@ struct WStringAsString; template struct DNA { + virtual ~DNA() {} + /** * @brief Common virtual read function for all DNA types */ @@ -52,6 +54,12 @@ struct DNA * @brief Common virtual write function for all DNA types */ virtual void write(IStreamWriter&) const=0; + /** + * @brief Common virtual binary size computation for all DNA types + * @param __isz initial cumulative value to add result to + * @return Cumulative size + */ + virtual size_t binarySize(size_t __isz) const=0; /** * @brief Template type signaling atdna to capture the value where it's used @@ -121,6 +129,20 @@ struct DNA * @brief Meta Template preventing atdna from emitting read/write implementations */ struct Delete {}; + + /** + * @brief Internal DNA helper for accumulating binarySize + * @param __isz initial size value + * @param v Vector to enumerate + * @return Cumulative total + */ + template + static size_t __EnumerateSize(size_t __isz, const T& v) + { + for (const auto& val : v) + __isz = val.binarySize(__isz); + return __isz; + } }; /** @@ -130,15 +152,19 @@ template struct Buffer : public DNA, public std::unique_ptr { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) { reset(new atUint8[sizeVar]); reader.readUBytesToBuf(get(), sizeVar); } - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const { writer.writeUBytes(get(), sizeVar); } + size_t binarySize(size_t __isz) const + { + return __isz + sizeVar; + } }; /** @@ -148,13 +174,15 @@ template struct String : public DNA, public std::string { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) {this->assign(std::move(reader.readString(sizeVar)));} - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const {writer.writeString(*this, sizeVar);} - inline std::string& operator=(const std::string& __str) + size_t binarySize(size_t __isz) const + {return __isz + ((sizeVar<0)?(this->size()+1):sizeVar);} + std::string& operator=(const std::string& __str) {return this->assign(__str);} - inline std::string& operator=(std::string&& __str) + std::string& operator=(std::string&& __str) {this->swap(__str); return *this;} }; @@ -165,19 +193,21 @@ template struct WString : public DNA, public std::wstring { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) { reader.setEndian(VE); this->assign(std::move(reader.readWString(sizeVar))); } - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const { writer.setEndian(VE); writer.writeWString(*this, sizeVar); } - inline std::wstring& operator=(const std::wstring& __str) + size_t binarySize(size_t __isz) const + {return __isz + (((sizeVar<0)?(this->size()+1):sizeVar)*2);} + std::wstring& operator=(const std::wstring& __str) {return this->assign(__str);} - inline std::wstring& operator=(std::wstring&& __str) + std::wstring& operator=(std::wstring&& __str) {this->swap(__str); return *this;} }; @@ -188,13 +218,15 @@ template struct WStringAsString : public DNA, public std::string { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) {*this = reader.readWStringAsString(sizeVar);} - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const {writer.writeStringAsWString(*this, sizeVar);} - inline std::string& operator=(const std::string& __str) + size_t binarySize(size_t __isz) const + {return __isz + (((sizeVar<0)?(this->size()+1):sizeVar)*2);} + std::string& operator=(const std::string& __str) {return this->assign(__str);} - inline std::string& operator=(std::string&& __str) + std::string& operator=(std::string&& __str) {this->swap(__str); return *this;} }; @@ -202,11 +234,13 @@ struct WStringAsString : public DNA, public std::string #define DECL_DNA \ void read(Athena::io::IStreamReader&); \ void write(Athena::io::IStreamWriter&) const; \ + size_t binarySize(size_t __isz) const; /** Macro to automatically declare read/write methods and prevent outputting implementation */ #define DECL_EXPLICIT_DNA \ void read(Athena::io::IStreamReader&); \ void write(Athena::io::IStreamWriter&) const; \ + size_t binarySize(size_t __isz) const; \ Delete __dna_delete; /** Macro to supply count variable to atdna and mute it for other compilers */ diff --git a/include/Athena/DNAYaml.hpp b/include/Athena/DNAYaml.hpp index 6294ae3..4b7e456 100644 --- a/include/Athena/DNAYaml.hpp +++ b/include/Athena/DNAYaml.hpp @@ -1049,6 +1049,8 @@ struct WStringAsStringYaml; template struct DNAYaml : DNA { + virtual ~DNAYaml() {} + virtual void toYAML(YAMLDocWriter& out) const=0; virtual void fromYAML(YAMLDocReader& in)=0; static const char* DNAType() {return nullptr;} @@ -1230,18 +1232,22 @@ template struct BufferYaml : public DNAYaml, public std::unique_ptr { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) { reset(new atUint8[sizeVar]); reader.readUBytesToBuf(get(), sizeVar); } - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const { writer.writeUBytes(get(), sizeVar); } - inline void fromYAML(Athena::io::YAMLDocReader& reader) + size_t binarySize(size_t __isz) const + { + return __isz + sizeVar; + } + void fromYAML(Athena::io::YAMLDocReader& reader) {*this = reader.readUBytes(nullptr);} - inline void toYAML(Athena::io::YAMLDocWriter& writer) const + void toYAML(Athena::io::YAMLDocWriter& writer) const {writer.writeUBytes(nullptr, *this, sizeVar);} }; @@ -1249,17 +1255,19 @@ template struct StringYaml : public DNAYaml, public std::string { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) {this->assign(std::move(reader.readString(sizeVar)));} - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const {writer.writeString(*this, sizeVar);} - inline void fromYAML(Athena::io::YAMLDocReader& reader) + size_t binarySize(size_t __isz) const + {return __isz + ((sizeVar<0)?(this->size()+1):sizeVar);} + void fromYAML(Athena::io::YAMLDocReader& reader) {this->assign(std::move(reader.readString(nullptr)));} - inline void toYAML(Athena::io::YAMLDocWriter& writer) const + void toYAML(Athena::io::YAMLDocWriter& writer) const {writer.writeString(nullptr, *this);} - inline std::string& operator=(const std::string& __str) + std::string& operator=(const std::string& __str) {return this->assign(__str);} - inline std::string& operator=(std::string&& __str) + std::string& operator=(std::string&& __str) {this->swap(__str); return *this;} }; @@ -1267,23 +1275,25 @@ template struct WStringYaml : public DNAYaml, public std::wstring { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) { reader.setEndian(VE); this->assign(std::move(reader.readWString(sizeVar))); } - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const { writer.setEndian(VE); writer.writeWString(*this, sizeVar); } - inline void fromYAML(Athena::io::YAMLDocReader& reader) + size_t binarySize(size_t __isz) const + {return __isz + (((sizeVar<0)?(this->size()+1):sizeVar)*2);} + void fromYAML(Athena::io::YAMLDocReader& reader) {this->assign(std::move(reader.readWString(nullptr)));} - inline void toYAML(Athena::io::YAMLDocWriter& writer) const + void toYAML(Athena::io::YAMLDocWriter& writer) const {writer.writeWString(nullptr, *this);} - inline std::wstring& operator=(const std::wstring& __str) + std::wstring& operator=(const std::wstring& __str) {return this->assign(__str);} - inline std::wstring& operator=(std::wstring&& __str) + std::wstring& operator=(std::wstring&& __str) {this->swap(__str); return *this;} }; @@ -1291,17 +1301,19 @@ template struct WStringAsStringYaml : public DNAYaml, public std::string { typename DNA::Delete expl; - inline void read(IStreamReader& reader) + void read(IStreamReader& reader) {*this = reader.readWStringAsString(sizeVar);} - inline void write(IStreamWriter& writer) const + void write(IStreamWriter& writer) const {writer.writeStringAsWString(*this, sizeVar);} - inline void fromYAML(Athena::io::YAMLDocReader& reader) + size_t binarySize(size_t __isz) const + {return __isz + (((sizeVar<0)?(this->size()+1):sizeVar)*2);} + void fromYAML(Athena::io::YAMLDocReader& reader) {this->assign(std::move(reader.readString(nullptr)));} - inline void toYAML(Athena::io::YAMLDocWriter& writer) const + void toYAML(Athena::io::YAMLDocWriter& writer) const {writer.writeString(nullptr, *this);} - inline std::string& operator=(const std::string& __str) + std::string& operator=(const std::string& __str) {return this->assign(__str);} - inline std::string& operator=(std::string&& __str) + std::string& operator=(std::string&& __str) {this->swap(__str); return *this;} };