From 02d80af0953fc22c3182a70e23eda79cab5c70ed Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 6 Oct 2026 15:48:05 +0100 Subject: [PATCH 01/37] Rename all String()` with `format_as()` --- include/bout/bout_enum_class.hxx | 12 +++++------ include/bout/bout_types.hxx | 27 ++++++++---------------- include/bout/deriv_store.hxx | 36 ++++++++++++++++---------------- include/bout/difops.hxx | 18 ++++++++-------- include/bout/field2d.hxx | 4 ++-- include/bout/field3d.hxx | 6 +++--- include/bout/fieldperp.hxx | 4 ++-- include/bout/interpolation.hxx | 2 +- include/bout/mesh.hxx | 2 -- include/bout/options.hxx | 15 ++++++------- include/bout/sys/variant.hxx | 2 +- include/bout/utils.hxx | 16 +++++++------- include/bout/vecops.hxx | 4 ++-- src/field/field2d.cxx | 4 ++-- src/field/field3d.cxx | 12 +++++------ src/field/fieldperp.cxx | 4 ++-- src/sys/bout_types.cxx | 22 +++++++++---------- src/sys/options.cxx | 2 -- 18 files changed, 88 insertions(+), 104 deletions(-) diff --git a/include/bout/bout_enum_class.hxx b/include/bout/bout_enum_class.hxx index ce088a4628..c248136374 100644 --- a/include/bout/bout_enum_class.hxx +++ b/include/bout/bout_enum_class.hxx @@ -64,12 +64,12 @@ #define BOUT_MAKE_FROMSTRING_NAME(enumname) enumname##FromString -/// Create an enum class with toString and FromString functions, and an +/// Create an enum class with format_as and FromString functions, and an /// Options::as overload to read the enum #define BOUT_ENUM_CLASS(enumname, ...) \ enum class enumname : std::uint8_t { __VA_ARGS__ }; \ \ - inline std::string toString(enumname e) { \ + inline std::string format_as(enumname e) { \ \ const static std::map toString_map = { \ BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, enumname, __VA_ARGS__)}; \ @@ -102,17 +102,17 @@ } \ \ inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ - return out << toString(e); \ + return out << format_as(e); \ } -/// Create an enum class with toString and FromString functions, and an +/// Create an enum class with format_as and FromString functions, and an /// Options::as overload to read the enum #define BOUT_ENUM_CLASS_NS(ns, enumname, ...) \ namespace ns { \ enum class enumname : std::uint8_t { __VA_ARGS__ }; \ } \ \ - inline std::string toString(ns::enumname e) { \ + inline std::string format_as(const ns::enumname& e) { \ \ const static std::map toString_map = { \ BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, ns::enumname, __VA_ARGS__)}; \ @@ -145,7 +145,7 @@ } \ namespace ns { \ inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ - return out << toString(e); \ + return out << format_as(e); \ } \ } diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index 41ca37c99d..a60edf02bf 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -49,9 +49,8 @@ constexpr CELL_LOC CELL_YLOW = CELL_LOC::ylow; constexpr CELL_LOC CELL_ZLOW = CELL_LOC::zlow; constexpr CELL_LOC CELL_VSHIFT = CELL_LOC::vshift; -std::string toString(CELL_LOC location); +std::string format_as(const CELL_LOC& location); CELL_LOC CELL_LOCFromString(const std::string& location_string); -inline std::string format_as(const CELL_LOC& loc) { return toString(loc); } /// Differential methods. Both central and upwind enum class DIFF_METHOD { deflt, u1, u2, c2, w2, w3, c4, u3, fft, split, s2 }; @@ -67,8 +66,7 @@ constexpr DIFF_METHOD DIFF_FFT = DIFF_METHOD::fft; constexpr DIFF_METHOD DIFF_SPLIT = DIFF_METHOD::split; constexpr DIFF_METHOD DIFF_S2 = DIFF_METHOD::s2; -std::string toString(DIFF_METHOD location); -inline std::string format_as(const DIFF_METHOD& method) { return toString(method); } +std::string format_as(const DIFF_METHOD& method); /// Specify grid region for looping enum class REGION { all, nobndry, nox, noy, noz }; @@ -78,8 +76,7 @@ constexpr REGION RGN_NOX = REGION::nox; constexpr REGION RGN_NOY = REGION::noy; constexpr REGION RGN_NOZ = REGION::noz; -std::string toString(REGION region); -inline std::string format_as(const REGION& region) { return toString(region); } +std::string format_as(const REGION& region); /// To identify particular directions (in index space): /// - X, Y, Z are the coordinate directions @@ -90,8 +87,7 @@ inline std::string format_as(const REGION& region) { return toString(region); } /// field-aligned enum class DIRECTION { X, Y, Z, YAligned, YOrthogonal }; -std::string toString(DIRECTION direction); -inline std::string format_as(const DIRECTION& dir) { return toString(dir); } +std::string format_as(const DIRECTION& dir); /// Identify kind of a field's y-direction /// - Standard is the default for the Mesh/Coordinates/ParallelTransform @@ -99,9 +95,8 @@ inline std::string format_as(const DIRECTION& dir) { return toString(dir); } /// coordinates enum class YDirectionType { Standard, Aligned }; -std::string toString(YDirectionType d); YDirectionType YDirectionTypeFromString(const std::string& y_direction_string); -inline std::string format_as(const YDirectionType& d) { return toString(d); } +std::string format_as(const YDirectionType& d); /// Identify kind of a field's z-direction /// - Standard is the default @@ -110,9 +105,8 @@ inline std::string format_as(const YDirectionType& d) { return toString(d); } /// Field2D) enum class ZDirectionType { Standard, Average }; -std::string toString(ZDirectionType d); ZDirectionType ZDirectionTypeFromString(const std::string& z_direction_string); -inline std::string format_as(const ZDirectionType& d) { return toString(d); } +std::string format_as(const ZDirectionType& d); /// Container for direction types struct DirectionTypes { @@ -120,8 +114,7 @@ struct DirectionTypes { ZDirectionType z; }; -std::string toString(const DirectionTypes& dir); -inline std::string format_as(const DirectionTypes& dir) { return toString(dir); } +std::string format_as(const DirectionTypes& dir); /// Check whether direction types are compatible, so two fields with attributes /// d1 and d2 respectively can be added, subtracted, etc. @@ -132,14 +125,12 @@ void swap(const DirectionTypes& first, const DirectionTypes& second); /// To identify valid staggering combinations enum class STAGGER { None, C2L, L2C }; -std::string toString(STAGGER stagger); -inline std::string format_as(const STAGGER& stagger) { return toString(stagger); } +std::string format_as(const STAGGER& stagger); /// To identify types of derivative method combinations enum class DERIV { Standard, StandardSecond, StandardFourth, Upwind, Flux }; -std::string toString(DERIV deriv); -inline std::string format_as(const DERIV& deriv) { return toString(deriv); } +std::string format_as(const DERIV& deriv); // A small struct that can be used to wrap a specific enum value, giving // it a unique type that can be passed as a valid type to templates and diff --git a/include/bout/deriv_store.hxx b/include/bout/deriv_store.hxx index 36412ec531..7687b824cb 100644 --- a/include/bout/deriv_store.hxx +++ b/include/bout/deriv_store.hxx @@ -87,7 +87,7 @@ struct DerivativeStore { STAGGER stagger = STAGGER::None) const { // Get the key - auto key = getKey(direction, stagger, toString(derivType)); + auto key = getKey(direction, stagger, format_as(derivType)); return isEmpty(key); } @@ -97,7 +97,7 @@ struct DerivativeStore { STAGGER stagger = STAGGER::None) const { // Get the key - auto key = getKey(direction, stagger, toString(derivType)); + auto key = getKey(direction, stagger, format_as(derivType)); if (isEmpty(key)) { return std::set{}; } else { @@ -112,11 +112,11 @@ struct DerivativeStore { // Introductory information output_info << "Available methods for derivative type '"; - output_info << toString(derivType); + output_info << format_as(derivType); output_info << "' in direction "; - output_info << toString(direction); + output_info << format_as(direction); output_info << " ( Staggering : "; - output_info << toString(stagger) << " ) : \n"; + output_info << format_as(stagger) << " ) : \n"; for (const auto& i : getAvailableMethods(derivType, direction, stagger)) { output_info << "\t" << i << "\n"; @@ -162,7 +162,7 @@ struct DerivativeStore { }; // Register this method name in lookup of known methods - registeredMethods[getKey(direction, stagger, toString(derivType))].insert(methodName); + registeredMethods[getKey(direction, stagger, format_as(derivType))].insert(methodName); }; /// Register a function with upwindFunc/fluxFunc interface. Which map is used @@ -196,7 +196,7 @@ struct DerivativeStore { }; // Register this method name in lookup of known methods - registeredMethods[getKey(direction, stagger, toString(derivType))].insert(methodName); + registeredMethods[getKey(direction, stagger, format_as(derivType))].insert(methodName); }; /// Templated versions of the above registration routines. @@ -225,7 +225,7 @@ struct DerivativeStore { DERIV derivType = DERIV::Standard) const { const auto realName = nameLookup( - name, defaultMethods.at(getKey(direction, stagger, toString(derivType)))); + name, defaultMethods.at(getKey(direction, stagger, format_as(derivType)))); const auto key = getKey(direction, stagger, realName); const storageType* theMap = nullptr; @@ -269,7 +269,7 @@ struct DerivativeStore { DERIV derivType = DERIV::Upwind) const { const auto realName = nameLookup( - name, defaultMethods.at(getKey(direction, stagger, toString(derivType)))); + name, defaultMethods.at(getKey(direction, stagger, format_as(derivType)))); const auto key = getKey(direction, stagger, realName); const storageType* theMap = nullptr; @@ -331,7 +331,7 @@ struct DerivativeStore { const auto theDirection = direction.first; const auto theDerivType = deriv.first; - const auto theDerivTypeString = toString(theDerivType); + const auto theDerivTypeString = format_as(theDerivType); // Note both staggered and unstaggered have the same fallback default currently std::string theDefault{ @@ -360,7 +360,7 @@ struct DerivativeStore { defaultMethods[getKey(theDirection, STAGGER::None, theDerivTypeString)] = theDefault; output_verbose << "The default method for derivative type " << theDerivTypeString - << " in direction " << toString(theDirection) << " is " + << " in direction " << format_as(theDirection) << " is " << theDefault << "\n"; //------------------------------------------------------------- @@ -384,7 +384,7 @@ struct DerivativeStore { defaultMethods[getKey(theDirection, STAGGER::C2L, theDerivTypeString)] = theDefault; output_verbose << "The default method for staggered derivative type " - << theDerivTypeString << " in direction " << toString(theDirection) + << theDerivTypeString << " in direction " << format_as(theDirection) << " is " << theDefault << "\n"; } } @@ -393,7 +393,7 @@ struct DerivativeStore { /// Provide a method to override/force a specific default method void forceDefaultMethod(std::string methodName, DERIV deriv, DIRECTION direction, STAGGER stagger = STAGGER::None) { - const auto key = getKey(direction, stagger, toString(deriv)); + const auto key = getKey(direction, stagger, format_as(deriv)); defaultMethods[key] = uppercase(methodName); } @@ -461,7 +461,7 @@ private: for (const auto& direction : directions) { for (const auto& deriv : derivTypes) { const auto theDirection = direction.first; - const auto theDerivTypeString = toString(deriv.first); + const auto theDerivTypeString = format_as(deriv.first); const std::string theDefault{uppercase(initialDefaultMethods[deriv.first])}; //------------------------------------------------------------- @@ -482,11 +482,11 @@ private: std::string getMethodName(std::string name, DIRECTION direction, STAGGER stagger = STAGGER::None) const { - return name + " (" + toString(direction) + ", " + toString(stagger) + ")"; + return name + " (" + format_as(direction) + ", " + format_as(stagger) + ")"; }; std::string nameLookup(const std::string name, const std::string defaultName) const { - return name != toString(DIFF_DEFAULT) ? name : defaultName; + return name != format_as(DIFF_DEFAULT) ? name : defaultName; } /// Provides a routine to produce a unique key given information @@ -503,8 +503,8 @@ private: // maps to store the different field types as the signature is // different. std::size_t result; - result = std::hash{}(toString(direction)); - result = result ^ std::hash{}(toString(stagger)); + result = std::hash{}(format_as(direction)); + result = result ^ std::hash{}(format_as(stagger)); result = result ^ std::hash{}(key); return result; } diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index e20aef2459..733a56d5d2 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -61,7 +61,7 @@ bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, DIFF_METHOD method) { - return Grad_par(var, outloc, toString(method)); + return Grad_par(var, outloc, format_as(method)); } Field3D Grad_par(const Field3DParallel& var, CELL_LOC outloc = CELL_DEFAULT, @@ -99,7 +99,7 @@ bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, inline bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Vpar_Grad_par(v, f, outloc, toString(method)); + return Vpar_Grad_par(v, f, outloc, format_as(method)); } Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, @@ -108,7 +108,7 @@ Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, inline Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Vpar_Grad_par(v, f, outloc, toString(method)); + return Vpar_Grad_par(v, f, outloc, format_as(method)); } /*! @@ -127,14 +127,14 @@ bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Div_par(f, outloc, toString(method)); + return Div_par(f, outloc, format_as(method)); } Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Div_par(f, outloc, toString(method)); + return Div_par(f, outloc, format_as(method)); } // Divergence of a parallel flow: Div(f*v) @@ -151,7 +151,7 @@ Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, inline Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Div_par_flux(v, f, outloc, toString(method)); + return Div_par_flux(v, f, outloc, format_as(method)); } /*! @@ -169,13 +169,13 @@ bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Grad2_par2(f, outloc, toString(method)); + return Grad2_par2(f, outloc, format_as(method)); } Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Grad2_par2(f, outloc, toString(method)); + return Grad2_par2(f, outloc, format_as(method)); } /*! @@ -220,7 +220,7 @@ Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field3DParallel& f, BOUT_ENUM_CLASS_NS(bout, ConductionMethod, Original, ProductJK, Harmonic); namespace bout { -inline std::string format_as(const ConductionMethod& method) { return toString(method); } +inline std::string format_as(const ConductionMethod& method) { return format_as(method); } } // namespace bout /// Version with energy flow diagnostic diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 6e89275ce8..3104d76277 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -604,10 +604,10 @@ inline Field2D& ddt(Field2D& f) { return *(f.timeDeriv()); } /// Alias for ddt that is more readable for constraints inline Field2D& residual(Field2D& f) { return *(f.timeDeriv()); } -/// toString template specialisation +/// format_as template specialisation /// Defined in utils.hxx template <> -inline std::string toString<>([[maybe_unused]] const Field2D& val) { +inline std::string format_as<>([[maybe_unused]] const Field2D& val) { return ""; } diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 04a566678e..0f0e7928e9 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -950,7 +950,7 @@ Field3D lowPass(const Field3D& var, int zmax, bool keep_zonal, [[deprecated("Please use a bool for `keep_zonal`")]] inline Field3D lowPass(const Field3D& var, int zmax, int keep_zonal, REGION rgn = RGN_ALL) { ASSERT0(static_cast(keep_zonal) == keep_zonal); - return lowPass(var, zmax, static_cast(keep_zonal), toString(rgn)); + return lowPass(var, zmax, static_cast(keep_zonal), format_as(rgn)); } /// Fourier low pass filtering. Removes modes higher than \p zmax @@ -998,10 +998,10 @@ inline Field3D& ddt(Field3D& f) { return *(f.timeDeriv()); } /// Alias for ddt that is more readable for constraints inline Field3D& residual(Field3D& f) { return *(f.timeDeriv()); } -/// toString template specialisation +/// format_as template specialisation /// Defined in utils.hxx template <> -inline std::string toString<>([[maybe_unused]] const Field3D& val) { +inline std::string format_as<>([[maybe_unused]] const Field3D& val) { return ""; } diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 2f8408d82b..6edb74950e 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -458,10 +458,10 @@ void invalidateGuards(FieldPerp& var); inline void invalidateGuards(FieldPerp& UNUSED(var)) {} #endif -/// toString template specialisation +/// format_as template specialisation /// Defined in utils.hxx template <> -inline std::string toString<>([[maybe_unused]] const FieldPerp& val) { +inline std::string format_as<>([[maybe_unused]] const FieldPerp& val) { return ""; } diff --git a/include/bout/interpolation.hxx b/include/bout/interpolation.hxx index bf9b1de466..6bcffce8cc 100644 --- a/include/bout/interpolation.hxx +++ b/include/bout/interpolation.hxx @@ -88,7 +88,7 @@ interp_to(const T& var, CELL_LOC loc, const std::string& region = "RGN_ALL") { T result{emptyFrom(var).setLocation(loc)}; // Staggered grids enabled, and need to perform interpolation - TRACE("Interpolating {} -> {}", toString(var.getLocation()), toString(loc)); + TRACE("Interpolating {} -> {}", format_as(var.getLocation()), format_as(loc)); if (region != "RGN_NOBNDRY") { // result is requested in some boundary region(s) diff --git a/include/bout/mesh.hxx b/include/bout/mesh.hxx index 3d630b5ce3..dd7ccbfc0b 100644 --- a/include/bout/mesh.hxx +++ b/include/bout/mesh.hxx @@ -88,8 +88,6 @@ public: BOUT_ENUM_CLASS(BoundaryParType, all, xin, xout, fwd, bwd, xin_fwd, xout_fwd, xin_bwd, xout_bwd, SIZE); -inline auto format_as(const BoundaryParType& type) { return toString(type); } - template using RegisterMesh = MeshFactory::RegisterInFactory; diff --git a/include/bout/options.hxx b/include/bout/options.hxx index b7eb9eb29c..ffb8d033df 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -608,9 +608,9 @@ public: if (bout::utils::variantEqualTo(attributes.at("source"), DEFAULT_SOURCE)) { // Check that the default values are the same if (!similar(val, def)) { - throw BoutException("Inconsistent default values for '{:s}': '{:s}' then '{:s}'", + throw BoutException("Inconsistent default values for '{}': '{}' then '{}'", full_name, bout::utils::variantToString(value), - toString(def)); + def); } } return val; @@ -667,7 +667,7 @@ public: if (!similar(val, def)) { throw BoutException("Inconsistent default values for '{:s}': '{:s}' then '{:s}'", full_name, bout::utils::variantToString(value), - toString(def)); + format_as(def)); } } return val; @@ -913,13 +913,13 @@ private: << bout::utils::variantToString(value) << " (" << bout::utils::variantToString(attributes["source"]) << _(") overwritten with:") << "\n" - << "\t\t" << full_name << " = " << toString(val) << " (" << source + << "\t\t" << full_name << " = " << fmt::to_string(val) << " (" << source << ")\n"; } else { throw BoutException( _f("Options: Setting a value from same source ({:s}) to new value " "'{:s}' - old value was '{:s}'."), - source, toString(val), bout::utils::variantToString(value)); + source, val, bout::utils::variantToString(value)); } } } @@ -1018,9 +1018,6 @@ auto Options::as(const Tensor& similar_to) const -> Tensor; template <> auto Options::as(const Tensor& similar_to) const -> Tensor; -/// Convert \p value to string -std::string toString(const Options& value); - /// Save the parallel fields void saveParallel(Options& opt, const std::string& name, const Field3D& tosave); @@ -1030,7 +1027,7 @@ void saveParallel(Options& opt, const std::string& name, const Field3D& tosave); /// convertible to an `Options`, and we want _exactly_ `Options` template > inline std::ostream& operator<<(std::ostream& out, const T& value) { - return out << toString(value); + return out << format_as(value); } namespace bout { diff --git a/include/bout/sys/variant.hxx b/include/bout/sys/variant.hxx index 1b541f096d..a6c2307a0a 100644 --- a/include/bout/sys/variant.hxx +++ b/include/bout/sys/variant.hxx @@ -145,7 +145,7 @@ namespace details { struct ToString { template std::string operator()(T&& val) { - return toString(std::forward(val)); + return fmt::to_string(std::forward(val)); } }; diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 420ded10d6..22e1f6c856 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -524,34 +524,34 @@ char* copy_string(const char* s); /// Convert a value to a string /// by writing to a stringstream template -std::string toString(const T& val) { +std::string format_as(const T& val) { std::stringstream ss; ss << val; return ss.str(); } /// Simple case where input is already a string -/// This is so that toString can be used in templates +/// This is so that format_as can be used in templates /// where the type may be std::string. -inline std::string toString(const std::string& val) { return val; } +inline std::string format_as(const std::string& val) { return val; } template -inline std::string toString(const Array& UNUSED(val)) { +inline std::string format_as(const Array& UNUSED(val)) { return ""; } template -inline std::string toString(const Matrix& UNUSED(val)) { +inline std::string format_as(const Matrix& UNUSED(val)) { return ""; } template -inline std::string toString(const Tensor& UNUSED(val)) { +inline std::string format_as(const Tensor& UNUSED(val)) { return ""; } /// Convert a bool to "true" or "false" -inline std::string toString(const bool& val) { +inline std::string format_as(const bool& val) { if (val) { return "true"; } @@ -560,7 +560,7 @@ inline std::string toString(const bool& val) { /// Convert a time stamp to a string /// This uses std::localtime and std::put_time -std::string toString(const time_t& time); +std::string format_as(const time_t& time); /*! * Convert a string to lower case diff --git a/include/bout/vecops.hxx b/include/bout/vecops.hxx index 9166503855..0c0c105326 100644 --- a/include/bout/vecops.hxx +++ b/include/bout/vecops.hxx @@ -64,7 +64,7 @@ Vector3D Grad(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, Vector3D Grad_perp(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline Vector3D Grad_perp(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Grad_perp(f, outloc, toString(method)); + return Grad_perp(f, outloc, format_as(method)); } Vector2D Grad_perp(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); @@ -92,7 +92,7 @@ Field3D Div(const Vector3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, inline Field3D Div(const Vector3D& v, const Field3D& f, CELL_LOC outloc, DIFF_METHOD method = DIFF_DEFAULT) { - return Div(v, f, outloc, toString(method)); + return Div(v, f, outloc, format_as(method)); } /// Curl of a vector diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index 6025909550..53434d4c2f 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -119,7 +119,7 @@ Field2D* Field2D::timeDeriv() { ////////////// Indexing /////////////////// const Region& Field2D::getRegion(REGION region) const { - return fieldmesh->getRegion2D(toString(region)); + return fieldmesh->getRegion2D(format_as(region)); } const Region& Field2D::getRegion(const std::string& region_name) const { return fieldmesh->getRegion2D(region_name); @@ -380,7 +380,7 @@ bool operator==(const Field2D& a, const Field2D& b) { } std::ostream& operator<<(std::ostream& out, const Field2D& value) { - out << toString(value); + out << format_as(value); return out; } diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 16b9e7a50b..7081ec72a4 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -264,14 +264,14 @@ const BoutReal& Field3D::operator()(const Ind2D& d, int jz) const { } const Region& Field3D::getRegion(REGION region) const { - return fieldmesh->getRegion3D(toString(region)); + return fieldmesh->getRegion3D(format_as(region)); } const Region& Field3D::getRegion(const std::string& region_name) const { return fieldmesh->getRegion3D(region_name); } const Region& Field3D::getRegion2D(REGION region) const { - return fieldmesh->getRegion2D(toString(region)); + return fieldmesh->getRegion2D(format_as(region)); } const Region& Field3D::getRegion2D(const std::string& region_name) const { return fieldmesh->getRegion2D(region_name); @@ -715,7 +715,7 @@ Field3D filter(const Field3D& var, int N0, const std::string& rgn) { Field3D result{emptyFrom(var)}; - const auto region_str = toString(rgn); + const auto region_str = format_as(rgn); // Only allow a whitelist of regions for now ASSERT2(region_str == "RGN_ALL" || region_str == "RGN_NOBNDRY" @@ -765,7 +765,7 @@ Field3D lowPass(const Field3D& var, int zmax, bool keep_zonal, const std::string Field3D result{emptyFrom(var)}; - const auto region_str = toString(rgn); + const auto region_str = format_as(rgn); // Only allow a whitelist of regions for now ASSERT2(region_str == "RGN_ALL" || region_str == "RGN_NOBNDRY" @@ -829,7 +829,7 @@ void shiftZ(Field3D& var, int jx, int jy, double zangle) { } void shiftZ(Field3D& var, double zangle, const std::string& rgn) { - const auto region_str = toString(rgn); + const auto region_str = format_as(rgn); // Only allow a whitelist of regions for now ASSERT2(region_str == "RGN_ALL" || region_str == "RGN_NOBNDRY" @@ -906,7 +906,7 @@ bool operator==(const Field3D& a, const Field3D& b) { } std::ostream& operator<<(std::ostream& out, const Field3D& value) { - out << toString(value); + out << format_as(value); return out; } diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index bca551c29d..a5c9931f51 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -107,7 +107,7 @@ FieldPerp& FieldPerp::operator=(const BoutReal rhs) { } const Region& FieldPerp::getRegion(REGION region) const { - return fieldmesh->getRegionPerp(toString(region)); + return fieldmesh->getRegionPerp(format_as(region)); } const Region& FieldPerp::getRegion(const std::string& region_name) const { return fieldmesh->getRegionPerp(region_name); @@ -245,7 +245,7 @@ bool operator==(const FieldPerp& a, const FieldPerp& b) { } std::ostream& operator<<(std::ostream& out, const FieldPerp& value) { - out << toString(value); + out << format_as(value); return out; } diff --git a/src/sys/bout_types.cxx b/src/sys/bout_types.cxx index cbcf19e996..9c43a104b1 100644 --- a/src/sys/bout_types.cxx +++ b/src/sys/bout_types.cxx @@ -25,7 +25,7 @@ const T& safeAt(const std::map& mymap, const std::string& s) { } } // namespace -std::string toString(CELL_LOC location) { +std::string format_as(const CELL_LOC& location) { const static std::map CELL_LOCtoString = { ENUMSTR(CELL_DEFAULT), ENUMSTR(CELL_CENTRE), ENUMSTR(CELL_XLOW), @@ -43,17 +43,17 @@ CELL_LOC CELL_LOCFromString(const std::string& location_string) { return safeAt(stringtoCELL_LOC, location_string); } -std::string toString(DIFF_METHOD location) { +std::string format_as(DIFF_METHOD method) { const static std::map DIFF_METHODtoString = { {DIFF_DEFAULT, "DEFAULT"}, {DIFF_U1, "U1"}, {DIFF_U2, "U2"}, {DIFF_U3, "U3"}, {DIFF_C2, "C2"}, {DIFF_C4, "C4"}, {DIFF_S2, "S2"}, {DIFF_W2, "W2"}, {DIFF_W3, "W3"}, {DIFF_FFT, "FFT"}, {DIFF_SPLIT, "SPLIT"}}; - return safeAt(DIFF_METHODtoString, location); + return safeAt(DIFF_METHODtoString, method); } -std::string toString(REGION region) { +std::string format_as(const REGION& region) { const static std::map REGIONtoString = { ENUMSTR(RGN_ALL), ENUMSTR(RGN_NOBNDRY), ENUMSTR(RGN_NOX), ENUMSTR(RGN_NOY), @@ -61,7 +61,7 @@ std::string toString(REGION region) { return safeAt(REGIONtoString, region); } -std::string toString(DIRECTION direction) { +std::string format_as(const DIRECTION& direction) { const static std::map DIRECTIONtoString = { {DIRECTION::X, "X"}, @@ -73,8 +73,8 @@ std::string toString(DIRECTION direction) { return safeAt(DIRECTIONtoString, direction); } -std::string toString(const DirectionTypes& dir) { - return "{" + toString(dir.y) + ", " + toString(dir.z) + "}"; +std::string format_as(const DirectionTypes& dir) { + return "{" + format_as(dir.y) + ", " + format_as(dir.z) + "}"; } void swap(DirectionTypes& first, DirectionTypes& second) { @@ -119,7 +119,7 @@ bool areDirectionsCompatible(const DirectionTypes& d1, const DirectionTypes& d2) return false; } -std::string toString(STAGGER stagger) { +std::string format_as(const STAGGER& stagger) { const static std::map STAGGERtoString = { {STAGGER::None, "No staggering"}, @@ -129,7 +129,7 @@ std::string toString(STAGGER stagger) { return safeAt(STAGGERtoString, stagger); } -std::string toString(DERIV deriv) { +std::string format_as(const DERIV& deriv) { const static std::map DERIVtoString = { {DERIV::Standard, "Standard"}, @@ -141,7 +141,7 @@ std::string toString(DERIV deriv) { return safeAt(DERIVtoString, deriv); } -std::string toString(YDirectionType d) { +std::string format_as(const YDirectionType& d) { const static std::map YDirectionTypeToString = { {YDirectionType::Standard, "Standard"}, {YDirectionType::Aligned, "Aligned"}}; @@ -157,7 +157,7 @@ YDirectionType YDirectionTypeFromString(const std::string& y_direction_string) { return safeAt(stringToYDirectionType, y_direction_string); } -std::string toString(ZDirectionType d) { +std::string format_as(const ZDirectionType& d) { const static std::map ZDirectionTypeToString = { {ZDirectionType::Standard, "Standard"}, {ZDirectionType::Average, "Average"}}; diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 09e5ba1ba4..e10f786321 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -1197,8 +1197,6 @@ bout::details::OptionsFormatterBase::format(const Options& options, return ctx.out(); } -std::string toString(const Options& value) { return fmt::format("{}", value); } - namespace bout { void checkForUnusedOptions() { auto& options = Options::root(); From ef70e853f1d172098fde7ab35b17d9b379425ac8 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 6 Oct 2026 17:13:52 +0100 Subject: [PATCH 02/37] Disable the range formatter for Field types so that {fmt} will not treat them as ranges so that it uses `format_as()`` instead. --- include/bout/field2d.hxx | 6 ++++++ include/bout/field3d.hxx | 3 +++ include/bout/fieldperp.hxx | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 3104d76277..4575d0ae7f 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -43,6 +43,9 @@ class Field2D; #include "bout/fieldperp.hxx" #include "bout/region.hxx" +#include +#include + #include #include #include @@ -617,4 +620,7 @@ bool operator==(const Field2D& a, const Field2D& b); std::ostream& operator<<(std::ostream& out, const Field2D& value); +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template <> struct fmt::is_range : std::false_type {}; + #endif /* BOUT_FIELD2D_H */ diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 0f0e7928e9..14b2ac0501 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1237,4 +1237,7 @@ filledFrom(const Field3DParallel& f, return result; } +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template <> struct fmt::is_range : std::false_type {}; + #endif /* BOUT_FIELD3D_H */ diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 6edb74950e..bfb1aa862c 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -38,6 +38,9 @@ class FieldPerp; #include "bout/unused.hxx" #include "bout/utils.hxx" +#include +#include + #include #include #include @@ -482,4 +485,7 @@ struct is_expr_fieldperp> || (is_expr_fieldperp_v> && is_expr_constant_v>)> {}; +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template <> struct fmt::is_range : std::false_type {}; + #endif From 415a5a62716a7f0009cd11dc2b2a5be21923f798 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 6 Oct 2026 17:32:55 +0100 Subject: [PATCH 03/37] Use a fmt::formatter for Field2D, Field3D, FieldPerp instead of `format_as()` Disable the range formatter for Array, Matrix, and Tensor (so that {fmt} will not treat them as ranges so that it uses `format_as()`` instead). --- include/bout/field2d.hxx | 12 ++++++------ include/bout/field3d.hxx | 11 +++++------ include/bout/fieldperp.hxx | 11 +++++------ include/bout/utils.hxx | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 4575d0ae7f..902dd60304 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -43,6 +43,7 @@ class Field2D; #include "bout/fieldperp.hxx" #include "bout/region.hxx" +#include #include #include @@ -610,9 +611,11 @@ inline Field2D& residual(Field2D& f) { return *(f.timeDeriv()); } /// format_as template specialisation /// Defined in utils.hxx template <> -inline std::string format_as<>([[maybe_unused]] const Field2D& val) { - return ""; -} +struct fmt::formatter : fmt::formatter { + auto format(const Field2D&, format_context& ctx) const { + return fmt::formatter::format("", ctx); + } +}; /// Test if two fields are the same, by calculating /// the minimum absolute difference between them @@ -620,7 +623,4 @@ bool operator==(const Field2D& a, const Field2D& b); std::ostream& operator<<(std::ostream& out, const Field2D& value); -// Tell {fmt} not to treat these as ranges so it uses format_as() instead -template <> struct fmt::is_range : std::false_type {}; - #endif /* BOUT_FIELD2D_H */ diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 14b2ac0501..20123c63bc 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1001,9 +1001,11 @@ inline Field3D& residual(Field3D& f) { return *(f.timeDeriv()); } /// format_as template specialisation /// Defined in utils.hxx template <> -inline std::string format_as<>([[maybe_unused]] const Field3D& val) { - return ""; -} +struct fmt::formatter : fmt::formatter { + auto format(const Field3D&, format_context& ctx) const { + return fmt::formatter::format("", ctx); + } +}; /// Test if two fields are the same, by calculating /// the minimum absolute difference between them @@ -1237,7 +1239,4 @@ filledFrom(const Field3DParallel& f, return result; } -// Tell {fmt} not to treat these as ranges so it uses format_as() instead -template <> struct fmt::is_range : std::false_type {}; - #endif /* BOUT_FIELD3D_H */ diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index bfb1aa862c..70a995ca81 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -464,9 +464,11 @@ inline void invalidateGuards(FieldPerp& UNUSED(var)) {} /// format_as template specialisation /// Defined in utils.hxx template <> -inline std::string format_as<>([[maybe_unused]] const FieldPerp& val) { - return ""; -} +struct fmt::formatter : fmt::formatter { + auto format(const FieldPerp&, format_context& ctx) const { + return fmt::formatter::format("", ctx); + } +}; /// Test if two fields are the same, by checking that they are defined /// at the same y-index, and if the minimum absolute difference @@ -485,7 +487,4 @@ struct is_expr_fieldperp> || (is_expr_fieldperp_v> && is_expr_constant_v>)> {}; -// Tell {fmt} not to treat these as ranges so it uses format_as() instead -template <> struct fmt::is_range : std::false_type {}; - #endif diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 22e1f6c856..a95db873ee 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -37,6 +37,8 @@ #include "bout/region.hxx" #include "bout/unused.hxx" +#include + #include #include #include @@ -550,6 +552,22 @@ inline std::string format_as(const Tensor& UNUSED(val)) { return ""; } +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template +struct fmt::range_format_kind, Char> { + static constexpr auto value = fmt::range_format::disabled; +}; + +template +struct fmt::range_format_kind, Char> { + static constexpr auto value = fmt::range_format::disabled; +}; + +template +struct fmt::range_format_kind, Char> { + static constexpr auto value = fmt::range_format::disabled; +}; + /// Convert a bool to "true" or "false" inline std::string format_as(const bool& val) { if (val) { From b13c455048e52247d518864ef162f19b809754bc Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 7 Oct 2026 09:33:00 +0100 Subject: [PATCH 04/37] Replace more `toSring()` calls with `format_as()` --- include/bout/hypre_interface.hxx | 4 ++-- include/bout/index_derivs.hxx | 2 +- include/bout/region.hxx | 6 +++--- src/mesh/coordinates.cxx | 2 +- src/mesh/interpolation_xz.cxx | 4 ++-- src/mesh/parallel/shiftedmetric.cxx | 2 +- src/sys/options.cxx | 20 ++++++++++---------- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/bout/hypre_interface.hxx b/include/bout/hypre_interface.hxx index d235746250..797f6f4ac3 100644 --- a/include/bout/hypre_interface.hxx +++ b/include/bout/hypre_interface.hxx @@ -1143,7 +1143,7 @@ public: break; } default: { - throw BoutException("Unsupported hypre_solver_type {}", toString(solver_type)); + throw BoutException("Unsupported hypre_solver_type {}", solver_type); } } @@ -1178,7 +1178,7 @@ public: break; } default: { - throw BoutException("Unsupported hypre_solver_type {}", toString(solver_type)); + throw BoutException("Unsupported hypre_solver_type {}", solver_type); } } diff --git a/include/bout/index_derivs.hxx b/include/bout/index_derivs.hxx index a8d1bbb1d3..9677d1597e 100644 --- a/include/bout/index_derivs.hxx +++ b/include/bout/index_derivs.hxx @@ -68,7 +68,7 @@ inline std::ostream& operator<<(std::ostream& out, const metaData& meta) { out << ", "; out << "nGuards : " << meta.nGuards; out << ", "; - out << "type : " << toString(meta.derivType); + out << "type : " << format_as(meta.derivType); return out; } diff --git a/include/bout/region.hxx b/include/bout/region.hxx index 802b3758ba..88abec0b3f 100644 --- a/include/bout/region.hxx +++ b/include/bout/region.hxx @@ -364,16 +364,16 @@ using IndPerp = SpecificInd; using IndG3D = SpecificInd; /// Get string representation of Ind3D -inline std::string toString(const Ind3D& i) { +inline std::string format_as(const Ind3D& i) { return "(" + std::to_string(i.x()) + ", " + std::to_string(i.y()) + ", " + std::to_string(i.z()) + ")"; } /// Get string representation of Ind2D -inline std::string toString(const Ind2D& i) { +inline std::string format_as(const Ind2D& i) { return "(" + std::to_string(i.x()) + ", " + std::to_string(i.y()) + ")"; } /// Get string representation of IndPerp -inline std::string toString(const IndPerp& i) { +inline std::string format_as(const IndPerp& i) { return "(" + std::to_string(i.x()) + ", " + std::to_string(i.z()) + ")"; } diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 3292dd85e1..5c3d5c6350 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -624,7 +624,7 @@ void Coordinates::readParallelMetricComponents() { void Coordinates::outputVars(Options& output_options) { const Timer time("io"); const std::string loc_string = - (location == CELL_CENTRE) ? "" : "_" + toString(location); + (location == CELL_CENTRE) ? "" : "_" + format_as(location); output_options["dx" + loc_string].force(dx(), "Coordinates"); output_options["dy" + loc_string].force(dy(), "Coordinates"); diff --git a/src/mesh/interpolation_xz.cxx b/src/mesh/interpolation_xz.cxx index 04d20769e4..1a1ec67497 100644 --- a/src/mesh/interpolation_xz.cxx +++ b/src/mesh/interpolation_xz.cxx @@ -35,8 +35,8 @@ #include #include -void printLocation(const Field3D& var) { output << toString(var.getLocation()); } -void printLocation(const Field2D& var) { output << toString(var.getLocation()); } +void printLocation(const Field3D& var) { output << format_as(var.getLocation()); } +void printLocation(const Field2D& var) { output << format_as(var.getLocation()); } const Field3D interpolate(const Field3D& f, const Field3D& delta_x, const Field3D& delta_z) { diff --git a/src/mesh/parallel/shiftedmetric.cxx b/src/mesh/parallel/shiftedmetric.cxx index c98333edc3..9036b2e697 100644 --- a/src/mesh/parallel/shiftedmetric.cxx +++ b/src/mesh/parallel/shiftedmetric.cxx @@ -48,7 +48,7 @@ void ShiftedMetric::checkInputGrid() { void ShiftedMetric::outputVars(Options& output_options) { Timer time("io"); const std::string loc_string = - (location == CELL_CENTRE) ? "" : "_" + toString(location); + (location == CELL_CENTRE) ? "" : "_" + format_as(location); output_options["zShift" + loc_string].force(zShift, "ShiftedMetric"); } diff --git a/src/sys/options.cxx b/src/sys/options.cxx index e10f786321..0cdddc215b 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -312,25 +312,25 @@ bool Options::isSection(const std::string& name) const { template <> Options& Options::assign<>(Field2D val, std::string source) { - attributes["cell_location"] = toString(val.getLocation()); - attributes["direction_y"] = toString(val.getDirectionY()); - attributes["direction_z"] = toString(val.getDirectionZ()); + attributes["cell_location"] = format_as(val.getLocation()); + attributes["direction_y"] = format_as(val.getDirectionY()); + attributes["direction_z"] = format_as(val.getDirectionZ()); _set_no_check(std::move(val), std::move(source)); return *this; } template <> Options& Options::assign<>(Field3D val, std::string source) { - attributes["cell_location"] = toString(val.getLocation()); - attributes["direction_y"] = toString(val.getDirectionY()); - attributes["direction_z"] = toString(val.getDirectionZ()); + attributes["cell_location"] = format_as(val.getLocation()); + attributes["direction_y"] = format_as(val.getDirectionY()); + attributes["direction_z"] = format_as(val.getDirectionZ()); _set_no_check(std::move(val), std::move(source)); return *this; } template <> Options& Options::assign<>(FieldPerp val, std::string source) { - attributes["cell_location"] = toString(val.getLocation()); - attributes["direction_y"] = toString(val.getDirectionY()); - attributes["direction_z"] = toString(val.getDirectionZ()); + attributes["cell_location"] = format_as(val.getLocation()); + attributes["direction_y"] = format_as(val.getDirectionY()); + attributes["direction_z"] = format_as(val.getDirectionZ()); attributes["yindex_global"] = val.getGlobalIndex(); _set_no_check(std::move(val), std::move(source)); return *this; @@ -542,7 +542,7 @@ bool Options::as(const bool& UNUSED(similar_to)) const { value_used = true; - printNameValueSourceLine(*this, toString(result)); + printNameValueSourceLine(*this, format_as(result)); return result; } From c032889734a0eab55e47b520a76ace63b51bd8e6 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 7 Oct 2026 09:51:07 +0100 Subject: [PATCH 05/37] Move `format_as()` inside namespace `ns` in BOUT_ENUM_CLASS_NS alongside the enum class and operator<<. This resolves ADL name lookup failures where operator<< failed to resolve the correct `format_as()` overload for namespaced enum classes. --- include/bout/bout_enum_class.hxx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/include/bout/bout_enum_class.hxx b/include/bout/bout_enum_class.hxx index c248136374..0bfd657efd 100644 --- a/include/bout/bout_enum_class.hxx +++ b/include/bout/bout_enum_class.hxx @@ -110,19 +110,17 @@ #define BOUT_ENUM_CLASS_NS(ns, enumname, ...) \ namespace ns { \ enum class enumname : std::uint8_t { __VA_ARGS__ }; \ - } \ - \ - inline std::string format_as(const ns::enumname& e) { \ \ - const static std::map toString_map = { \ - BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, ns::enumname, __VA_ARGS__)}; \ + inline std::string format_as(const enumname& e) { \ + const static std::map toString_map = { \ + BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, enumname, __VA_ARGS__)}; \ auto found = toString_map.find(e); \ if (found == toString_map.end()) { \ throw BoutException("Did not find enum {:d}", static_cast(e)); \ } \ return found->second; \ } \ - namespace ns { \ + \ inline enumname BOUT_MAKE_FROMSTRING_NAME(enumname)(const std::string& s) { \ const static std::map fromString_map = { \ BOUT_ENUM_CLASS_MAP_ARGS(BOUT_STR_ENUM_CLASS, enumname, __VA_ARGS__)}; \ @@ -137,16 +135,15 @@ } \ return found->second; \ } \ + \ + inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ + return out << format_as(e); \ } \ + } /* end namespace ns */ \ \ template <> \ inline ns::enumname Options::as(const ns::enumname&) const { \ return ns::BOUT_MAKE_FROMSTRING_NAME(enumname)(this->as()); \ - } \ - namespace ns { \ - inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ - return out << format_as(e); \ - } \ } #endif // BOUT_ENUM_CLASS_H From d77ca70936b624e159f3fcf5d6b92e4afb039613 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 11:33:31 +0100 Subject: [PATCH 06/37] Replace more `toSring()` calls with `format_as()` --- src/solver/solver.cxx | 12 ++++++------ tests/unit/include/bout/test_bout_enum_class.cxx | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index df0013dd3b..1cfccfadf0 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -281,7 +281,7 @@ void Solver::add(Field3D& v, const std::string& name, const std::string& descrip if (mesh->StaggerGrids && (v.getLocation() != CELL_CENTRE)) { output_info.write("\tVariable {:s} shifted to {:s}\n", name, - toString(v.getLocation())); + format_as(v.getLocation())); ddt(v).setLocation(v.getLocation()); // Make sure both at the same location } @@ -605,7 +605,7 @@ int Solver::solve(int nout, BoutReal timestep) { } time_t start_time = time(nullptr); - output_progress.write(_f("\nRun started at : {:s}\n"), toString(start_time)); + output_progress.write(_f("\nRun started at : {:s}\n"), format_as(start_time)); Timer timer("run"); // Start timer @@ -651,7 +651,7 @@ int Solver::solve(int nout, BoutReal timestep) { status = run(); time_t end_time = time(nullptr); - output_progress.write(_f("\nRun finished at : {:s}\n"), toString(end_time)); + output_progress.write(_f("\nRun finished at : {:s}\n"), format_as(end_time)); output_progress.write(_("Run time : ")); int dt = end_time - start_time; @@ -1274,7 +1274,7 @@ std::vector Solver::getJacobianMetadata2D() co metadata.reserve(f2d.size()); for (int i = 0; i < static_cast(f2d.size()); ++i) { - metadata.push_back(JacobianVariableMetadata{i, f2d[i].name, toString(f2d[i].location), + metadata.push_back(JacobianVariableMetadata{i, f2d[i].name, format_as(f2d[i].location), f2d[i].evolve_bndry, f2d[i].constraint, f2d[i].description}); } @@ -1287,7 +1287,7 @@ std::vector Solver::getJacobianMetadata3D() co metadata.reserve(f3d.size()); for (int i = 0; i < static_cast(f3d.size()); ++i) { - metadata.push_back(JacobianVariableMetadata{i, f3d[i].name, toString(f3d[i].location), + metadata.push_back(JacobianVariableMetadata{i, f3d[i].name, format_as(f3d[i].location), f3d[i].evolve_bndry, f3d[i].constraint, f3d[i].description}); } @@ -1390,7 +1390,7 @@ std::string Solver::getJacobianMatrixFilename(const std::string& jacobian_export // The directory the output data is stored in const std::string datadir = Options::root()["datadir"]; const std::string stem = fmt::format("{}/{}_{}_{:06d}", datadir, jacobian_export_prefix, - toString(kind), this->jacobian_export_counter++); + format_as(kind), this->jacobian_export_counter++); return stem + (format == bout::PetscMatrixExportFormat::binary ? ".dat" : ".txt"); } diff --git a/tests/unit/include/bout/test_bout_enum_class.cxx b/tests/unit/include/bout/test_bout_enum_class.cxx index 83508a522e..009939a2b3 100644 --- a/tests/unit/include/bout/test_bout_enum_class.cxx +++ b/tests/unit/include/bout/test_bout_enum_class.cxx @@ -10,13 +10,13 @@ BOUT_ENUM_CLASS_NS(bout, TestEnum, foo, bar); BOUT_ENUM_CLASS_NS(bout::test, TestEnum, foo, bar); TEST(BoutEnumClassNS, toString) { - EXPECT_EQ(toString(bout::TestEnum::foo), "foo"); - EXPECT_EQ(toString(bout::TestEnum::bar), "bar"); + EXPECT_EQ(format_as(bout::TestEnum::foo), "foo"); + EXPECT_EQ(format_as(bout::TestEnum::bar), "bar"); } TEST(BoutEnumClass, toString) { - EXPECT_EQ(toString(TestEnum::foo), "foo"); - EXPECT_EQ(toString(TestEnum::bar), "bar"); + EXPECT_EQ(format_as(TestEnum::foo), "foo"); + EXPECT_EQ(format_as(TestEnum::bar), "bar"); } TEST(BoutEnumClassNS, fromString) { From e074c09c184859ab9d10d29885dc9044b698f25d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 11:35:08 +0100 Subject: [PATCH 07/37] Remove `format_as()` for ConductionMethod as automatically generated by macro. --- include/bout/difops.hxx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index 733a56d5d2..594a0d29b0 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -219,10 +219,6 @@ Field3D Div_par_K_Grad_par(const Field3DParallel& kY, const Field3DParallel& f, /// different results when coefficients or cell sizes vary strongly. BOUT_ENUM_CLASS_NS(bout, ConductionMethod, Original, ProductJK, Harmonic); -namespace bout { -inline std::string format_as(const ConductionMethod& method) { return format_as(method); } -} // namespace bout - /// Version with energy flow diagnostic /// For FCI fields, `flow_ylow` is currently returned as zero. Field3D From dcf4f0662c6376efaadccc81f28ff7a57eed7fb2 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 12:33:27 +0100 Subject: [PATCH 08/37] Replace `{:s}` with `{}` Type-specific format specifiers not always supported now that `toString()` is not being explicitly called. --- include/bout/options.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index ffb8d033df..3f24fd0b6d 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -917,8 +917,8 @@ private: << ")\n"; } else { throw BoutException( - _f("Options: Setting a value from same source ({:s}) to new value " - "'{:s}' - old value was '{:s}'."), + _f("Options: Setting a value from same source ({}) to new value " + "'{}' - old value was '{:s}'."), source, val, bout::utils::variantToString(value)); } } From c0cbb885716a806299c81aa2eb3d7b3a17cc322a Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 13:09:08 +0100 Subject: [PATCH 09/37] Replace more `toString()` calls with `format_as()` --- include/bout/field3d.hxx | 4 +++- tests/integrated/test-fci-boundary/get_par_bndry.cxx | 2 +- tests/integrated/test-invpar/test_invpar.cxx | 2 +- tests/unit/field/test_fieldperp.cxx | 4 ++-- tests/unit/sys/test_options.cxx | 2 +- tests/unit/sys/test_options_netcdf.cxx | 10 +++++----- tests/unit/sys/test_utils.cxx | 12 ++++++------ 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 20123c63bc..7d82c879bb 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -19,12 +19,14 @@ * along with BOUT++. If not, see . * **************************************************************************/ +#pragma once +#include "fmt/base.h" +#include "fmt/format.h" #include "bout/utils.hxx" #include class Field3D; -#pragma once #ifndef BOUT_FIELD3D_H #define BOUT_FIELD3D_H diff --git a/tests/integrated/test-fci-boundary/get_par_bndry.cxx b/tests/integrated/test-fci-boundary/get_par_bndry.cxx index fd3bc2889f..8d2d5a3315 100644 --- a/tests/integrated/test-fci-boundary/get_par_bndry.cxx +++ b/tests/integrated/test-fci-boundary/get_par_bndry.cxx @@ -22,7 +22,7 @@ int main(int argc, char** argv) { for (int i = 0; i < fields.size(); i++) { fields[i].allocate(); const auto boundary = static_cast(i); - const auto boundary_name = toString(boundary); + const auto boundary_name = format_as(boundary); mesh->communicate(fields[i]); for (auto& bndry_par : mesh->getBoundariesPar(boundary)) { output.write("{:s} region\n", boundary_name); diff --git a/tests/integrated/test-invpar/test_invpar.cxx b/tests/integrated/test-invpar/test_invpar.cxx index 99b4e99c5c..5ac808efc9 100644 --- a/tests/integrated/test-invpar/test_invpar.cxx +++ b/tests/integrated/test-invpar/test_invpar.cxx @@ -16,7 +16,7 @@ int test(const std::string& acoef, const std::string& bcoef, const std::string& BoutReal tol, FieldFactory& f, CELL_LOC location) { output_error.write( "acoef={:s} bcoef={:s} ccoef={:s} dcoef={:s} ecoef{:s} with {:s} at {:s} ...", - acoef, bcoef, ccoef, dcoef, ecoef, func, toString(location)); + acoef, bcoef, ccoef, dcoef, ecoef, func, format_as(location)); auto inv = InvertPar::create(nullptr, location, mesh); Field2D A = f.create2D(acoef, nullptr, nullptr, location); diff --git a/tests/unit/field/test_fieldperp.cxx b/tests/unit/field/test_fieldperp.cxx index 754316e077..c85a322864 100644 --- a/tests/unit/field/test_fieldperp.cxx +++ b/tests/unit/field/test_fieldperp.cxx @@ -1875,7 +1875,7 @@ TEST_F(FieldPerpTest, ToString) { // Just check we can call toString const std::string expected = ""; const FieldPerp field{}; - EXPECT_EQ(toString(field), expected); + EXPECT_EQ(format_as(field), expected); } TEST_F(FieldPerpTest, OperatorStream) { @@ -1883,7 +1883,7 @@ TEST_F(FieldPerpTest, OperatorStream) { const FieldPerp field{}; std::stringstream stream; stream << field; - EXPECT_EQ(stream.str(), toString(field)); + EXPECT_EQ(stream.str(), format_as(field)); } TEST_F(FieldPerpTest, Equality) { diff --git a/tests/unit/sys/test_options.cxx b/tests/unit/sys/test_options.cxx index f4946b9f93..4d3cff751b 100644 --- a/tests/unit/sys/test_options.cxx +++ b/tests/unit/sys/test_options.cxx @@ -1137,7 +1137,7 @@ value4 = 3.2 value6 = 12 )"; - EXPECT_EQ(toString(option), expected); + EXPECT_EQ(format_as(option), expected); } TEST_F(OptionsTest, InvalidFormat) { diff --git a/tests/unit/sys/test_options_netcdf.cxx b/tests/unit/sys/test_options_netcdf.cxx index b16d838b54..5de1087ed4 100644 --- a/tests/unit/sys/test_options_netcdf.cxx +++ b/tests/unit/sys/test_options_netcdf.cxx @@ -189,7 +189,7 @@ TEST_F(OptionsNetCDFTest, Field2DWriteCellCentre) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), - toString(CELL_CENTRE)); + format_as(CELL_CENTRE)); } TEST_F(OptionsNetCDFTest, Field2DWriteCellYLow) { @@ -205,7 +205,7 @@ TEST_F(OptionsNetCDFTest, Field2DWriteCellYLow) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), - toString(CELL_YLOW)); + format_as(CELL_YLOW)); } TEST_F(OptionsNetCDFTest, Field3DWriteCellCentre) { @@ -221,7 +221,7 @@ TEST_F(OptionsNetCDFTest, Field3DWriteCellCentre) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - toString(CELL_CENTRE)); + format_as(CELL_CENTRE)); } TEST_F(OptionsNetCDFTest, Field3DWriteCellYLow) { @@ -237,7 +237,7 @@ TEST_F(OptionsNetCDFTest, Field3DWriteCellYLow) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - toString(CELL_YLOW)); + format_as(CELL_YLOW)); } TEST_F(OptionsNetCDFTest, FieldPerpWriteCellCentre) { @@ -259,7 +259,7 @@ TEST_F(OptionsNetCDFTest, FieldPerpWriteCellCentre) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["fperp"].attributes["cell_location"].as(), - toString(CELL_CENTRE)); + format_as(CELL_CENTRE)); EXPECT_EQ(data["fperp"].attributes["yindex_global"].as(), 2); } diff --git a/tests/unit/sys/test_utils.cxx b/tests/unit/sys/test_utils.cxx index 6d84813c48..1bd2c9ccbf 100644 --- a/tests/unit/sys/test_utils.cxx +++ b/tests/unit/sys/test_utils.cxx @@ -527,32 +527,32 @@ TEST(StringUtilitiesTest, BoolToString) { std::string true_string = "true"; std::string false_string = "false"; - EXPECT_EQ(true_string, toString(true)); - EXPECT_EQ(false_string, toString(false)); + EXPECT_EQ(true_string, format_as(true)); + EXPECT_EQ(false_string, format_as(false)); } TEST(StringUtilitiesTest, ConstCharToString) { - EXPECT_EQ(std::string("hello"), toString("hello")); + EXPECT_EQ(std::string("hello"), format_as("hello")); } TEST(StringUtilitiesTest, StringToString) { std::string test_string = "dlkjl872kj"; - EXPECT_EQ(test_string, toString(test_string)); + EXPECT_EQ(test_string, format_as(test_string)); } TEST(StringUtilitiesTest, IntToString) { int number_int = 42; std::string number_string = "42"; - EXPECT_EQ(number_string, toString(number_int)); + EXPECT_EQ(number_string, format_as(number_int)); } TEST(StringUtilitiesTest, RealToString) { BoutReal number_real = 3.142e8; std::string number_string = "3.142e+08"; - EXPECT_EQ(number_string, toString(number_real)); + EXPECT_EQ(number_string, format_as(number_real)); } TEST(StringUtilitiesTest, StringSplit) { From d10fc0bf15be27987e6de66b04ce3519bfae2e61 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 15:41:19 +0100 Subject: [PATCH 10/37] Correct fmt::formatter SFINAE parameter for Field types Move `std::enable_if_t` to the third template parameter (Enable). Also use `std::remove_cv_t` to ensure the trait matches const fields. This prevents {fmt} from ignoring the custom formatter and throwing compile-time "invalid format specifier" errors. --- include/bout/output_bout_types.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index 77ea8ebf2d..a72c74fc4f 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -116,7 +116,7 @@ constexpr auto parse_nonnegative_int(const char*& begin, const char* end, /// - ``e``: Number of elements at each edge to show /// - ``f``: Show full field template -struct fmt::formatter, char>> { +struct fmt::formatter>>> { private: fmt::formatter underlying; From bc87893784be5cab96c1d061d6ab6d8f46ba06ee Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 16:14:46 +0100 Subject: [PATCH 11/37] Delete catch-all `format_as` template from utils.hxx --- include/bout/utils.hxx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index a95db873ee..151b24042b 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -523,15 +523,6 @@ inline void checkData(BoutReal UNUSED(f)) {}; */ char* copy_string(const char* s); -/// Convert a value to a string -/// by writing to a stringstream -template -std::string format_as(const T& val) { - std::stringstream ss; - ss << val; - return ss.str(); -} - /// Simple case where input is already a string /// This is so that format_as can be used in templates /// where the type may be std::string. From 255b81bc449927e2bfcb7bb454cf47feded81f06 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 16:32:58 +0100 Subject: [PATCH 12/37] Remove redundant comments --- include/bout/field2d.hxx | 2 -- include/bout/field3d.hxx | 2 -- include/bout/fieldperp.hxx | 2 -- 3 files changed, 6 deletions(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 902dd60304..e5a4368f0b 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -608,8 +608,6 @@ inline Field2D& ddt(Field2D& f) { return *(f.timeDeriv()); } /// Alias for ddt that is more readable for constraints inline Field2D& residual(Field2D& f) { return *(f.timeDeriv()); } -/// format_as template specialisation -/// Defined in utils.hxx template <> struct fmt::formatter : fmt::formatter { auto format(const Field2D&, format_context& ctx) const { diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 7d82c879bb..9eb8b3e99e 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1000,8 +1000,6 @@ inline Field3D& ddt(Field3D& f) { return *(f.timeDeriv()); } /// Alias for ddt that is more readable for constraints inline Field3D& residual(Field3D& f) { return *(f.timeDeriv()); } -/// format_as template specialisation -/// Defined in utils.hxx template <> struct fmt::formatter : fmt::formatter { auto format(const Field3D&, format_context& ctx) const { diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 70a995ca81..430cb185a9 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -461,8 +461,6 @@ void invalidateGuards(FieldPerp& var); inline void invalidateGuards(FieldPerp& UNUSED(var)) {} #endif -/// format_as template specialisation -/// Defined in utils.hxx template <> struct fmt::formatter : fmt::formatter { auto format(const FieldPerp&, format_context& ctx) const { From e0d838adaebe30ca36e66091b4c5ef80fd5cc9c6 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 16:36:27 +0100 Subject: [PATCH 13/37] Replace calls to `format_as()` with `fmt::to_string` for types with custom `fmt::formatter` specializations rather than format_as overloads. --- include/bout/options.hxx | 4 ++-- src/field/field2d.cxx | 2 +- src/field/field3d.cxx | 2 +- src/field/fieldperp.cxx | 2 +- tests/unit/field/test_fieldperp.cxx | 4 ++-- tests/unit/sys/test_utils.cxx | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 3f24fd0b6d..60d3fd7a2e 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -1023,11 +1023,11 @@ void saveParallel(Options& opt, const std::string& name, const Field3D& tosave); /// Output a stringified \p value to a stream /// -/// This is templated to avoid implict casting: anything is +/// This is templated to avoid implicit casting: anything is /// convertible to an `Options`, and we want _exactly_ `Options` template > inline std::ostream& operator<<(std::ostream& out, const T& value) { - return out << format_as(value); + return out << fmt::to_string(value); } namespace bout { diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index 53434d4c2f..e48c15e274 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -380,7 +380,7 @@ bool operator==(const Field2D& a, const Field2D& b) { } std::ostream& operator<<(std::ostream& out, const Field2D& value) { - out << format_as(value); + out << fmt::to_string(value); return out; } diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 7081ec72a4..c83c0cd36e 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -906,7 +906,7 @@ bool operator==(const Field3D& a, const Field3D& b) { } std::ostream& operator<<(std::ostream& out, const Field3D& value) { - out << format_as(value); + out << fmt::to_string(value); return out; } diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index a5c9931f51..24e966093c 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -245,7 +245,7 @@ bool operator==(const FieldPerp& a, const FieldPerp& b) { } std::ostream& operator<<(std::ostream& out, const FieldPerp& value) { - out << format_as(value); + out << fmt::to_string(value); return out; } diff --git a/tests/unit/field/test_fieldperp.cxx b/tests/unit/field/test_fieldperp.cxx index c85a322864..e0e0f85850 100644 --- a/tests/unit/field/test_fieldperp.cxx +++ b/tests/unit/field/test_fieldperp.cxx @@ -1875,7 +1875,7 @@ TEST_F(FieldPerpTest, ToString) { // Just check we can call toString const std::string expected = ""; const FieldPerp field{}; - EXPECT_EQ(format_as(field), expected); + EXPECT_EQ(fmt::to_string(field), expected); } TEST_F(FieldPerpTest, OperatorStream) { @@ -1883,7 +1883,7 @@ TEST_F(FieldPerpTest, OperatorStream) { const FieldPerp field{}; std::stringstream stream; stream << field; - EXPECT_EQ(stream.str(), format_as(field)); + EXPECT_EQ(stream.str(), fmt::to_string(field)); } TEST_F(FieldPerpTest, Equality) { diff --git a/tests/unit/sys/test_utils.cxx b/tests/unit/sys/test_utils.cxx index 1bd2c9ccbf..22b27a4e7f 100644 --- a/tests/unit/sys/test_utils.cxx +++ b/tests/unit/sys/test_utils.cxx @@ -545,14 +545,14 @@ TEST(StringUtilitiesTest, IntToString) { int number_int = 42; std::string number_string = "42"; - EXPECT_EQ(number_string, format_as(number_int)); + EXPECT_EQ(number_string, fmt::to_string(number_int)); } TEST(StringUtilitiesTest, RealToString) { BoutReal number_real = 3.142e8; std::string number_string = "3.142e+08"; - EXPECT_EQ(number_string, format_as(number_real)); + EXPECT_EQ(number_string, fmt::to_string(number_real)); } TEST(StringUtilitiesTest, StringSplit) { From 2a5f3ec9250148f76d23203996b3f35bad1be25a Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 19:48:29 +0100 Subject: [PATCH 14/37] Wrap generic fmt::formatter for Fields in fmt namespace --- include/bout/output_bout_types.hxx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index a72c74fc4f..c606eaf265 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -115,10 +115,12 @@ constexpr auto parse_nonnegative_int(const char*& begin, const char* end, /// - ``#``: Plot slices as 2D heatmap /// - ``e``: Number of elements at each edge to show /// - ``f``: Show full field +namespace fmt { + template -struct fmt::formatter>>> { +struct formatter>>> { private: - fmt::formatter underlying; + formatter underlying; static constexpr auto default_region = "RGN_ALL"; std::string_view region = default_region; @@ -282,4 +284,6 @@ public: } }; +} // namespace fmt + #endif // OUTPUT_BOUT_TYPES_H From 1db89a8f3ef575157526468a01a75c0fa664bbd1 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 12 Oct 2026 21:26:58 +0100 Subject: [PATCH 15/37] Replace calls to `format_as()` with `fmt::to_string` for types with custom `fmt::formatter` specializations rather than format_as overloads. --- tests/unit/sys/test_options.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/sys/test_options.cxx b/tests/unit/sys/test_options.cxx index 4d3cff751b..6fd72af85c 100644 --- a/tests/unit/sys/test_options.cxx +++ b/tests/unit/sys/test_options.cxx @@ -1137,7 +1137,7 @@ value4 = 3.2 value6 = 12 )"; - EXPECT_EQ(format_as(option), expected); + EXPECT_EQ(fmt::to_string(option), expected); } TEST_F(OptionsTest, InvalidFormat) { From dff473e5c206f331177861f455f035f83ddcf0a3 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 13 Oct 2026 10:52:09 +0100 Subject: [PATCH 16/37] No need to call `format_as()` with BoutException --- include/bout/options.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 60d3fd7a2e..a2389232fd 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -667,7 +667,7 @@ public: if (!similar(val, def)) { throw BoutException("Inconsistent default values for '{:s}': '{:s}' then '{:s}'", full_name, bout::utils::variantToString(value), - format_as(def)); + def); } } return val; From 17cfc84c1e8a9f4c84bc31f4844ec13d1197548d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 13 Oct 2026 11:12:10 +0100 Subject: [PATCH 17/37] Call `fmt::to_string()` or `fmt::format()` not `format_as()` --- include/bout/bout_enum_class.hxx | 6 +-- include/bout/deriv_store.hxx | 37 ++++++++++--------- include/bout/difops.hxx | 16 ++++---- include/bout/field3d.hxx | 2 +- include/bout/index_derivs.hxx | 2 +- include/bout/interpolation.hxx | 2 +- include/bout/utils.hxx | 4 -- include/bout/vecops.hxx | 4 +- src/field/field2d.cxx | 2 +- src/field/field3d.cxx | 10 ++--- src/field/fieldperp.cxx | 2 +- src/mesh/coordinates.cxx | 2 +- src/mesh/interpolation_xz.cxx | 4 +- src/mesh/parallel/shiftedmetric.cxx | 2 +- src/solver/solver.cxx | 12 +++--- src/sys/bout_types.cxx | 2 +- src/sys/options.cxx | 20 +++++----- .../test-fci-boundary/get_par_bndry.cxx | 2 +- tests/integrated/test-invpar/test_invpar.cxx | 2 +- .../include/bout/test_bout_enum_class.cxx | 9 +++-- tests/unit/sys/test_options_netcdf.cxx | 10 ++--- tests/unit/sys/test_utils.cxx | 9 +++-- 22 files changed, 80 insertions(+), 81 deletions(-) diff --git a/include/bout/bout_enum_class.hxx b/include/bout/bout_enum_class.hxx index 0bfd657efd..50e0b7f8d0 100644 --- a/include/bout/bout_enum_class.hxx +++ b/include/bout/bout_enum_class.hxx @@ -69,7 +69,7 @@ #define BOUT_ENUM_CLASS(enumname, ...) \ enum class enumname : std::uint8_t { __VA_ARGS__ }; \ \ - inline std::string format_as(enumname e) { \ + inline std::string format_as(enumname e) { \ \ const static std::map toString_map = { \ BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, enumname, __VA_ARGS__)}; \ @@ -102,7 +102,7 @@ } \ \ inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ - return out << format_as(e); \ + return out << fmt::to_string(e); \ } /// Create an enum class with format_as and FromString functions, and an @@ -137,7 +137,7 @@ } \ \ inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ - return out << format_as(e); \ + return out << fmt::to_string(e); \ } \ } /* end namespace ns */ \ \ diff --git a/include/bout/deriv_store.hxx b/include/bout/deriv_store.hxx index 7687b824cb..885cc12b5d 100644 --- a/include/bout/deriv_store.hxx +++ b/include/bout/deriv_store.hxx @@ -29,6 +29,7 @@ #ifndef __DERIV_STORE_HXX__ #define __DERIV_STORE_HXX__ +#include "fmt/format.h" #include #include #include @@ -87,7 +88,7 @@ struct DerivativeStore { STAGGER stagger = STAGGER::None) const { // Get the key - auto key = getKey(direction, stagger, format_as(derivType)); + auto key = getKey(direction, stagger, fmt::to_string(derivType)); return isEmpty(key); } @@ -97,7 +98,7 @@ struct DerivativeStore { STAGGER stagger = STAGGER::None) const { // Get the key - auto key = getKey(direction, stagger, format_as(derivType)); + auto key = getKey(direction, stagger, fmt::to_string(derivType)); if (isEmpty(key)) { return std::set{}; } else { @@ -112,11 +113,11 @@ struct DerivativeStore { // Introductory information output_info << "Available methods for derivative type '"; - output_info << format_as(derivType); + output_info << fmt::to_string(derivType); output_info << "' in direction "; - output_info << format_as(direction); + output_info << fmt::to_string(direction); output_info << " ( Staggering : "; - output_info << format_as(stagger) << " ) : \n"; + output_info << fmt::to_string(stagger) << " ) : \n"; for (const auto& i : getAvailableMethods(derivType, direction, stagger)) { output_info << "\t" << i << "\n"; @@ -162,7 +163,7 @@ struct DerivativeStore { }; // Register this method name in lookup of known methods - registeredMethods[getKey(direction, stagger, format_as(derivType))].insert(methodName); + registeredMethods[getKey(direction, stagger, fmt::to_string(derivType))].insert(methodName); }; /// Register a function with upwindFunc/fluxFunc interface. Which map is used @@ -196,7 +197,7 @@ struct DerivativeStore { }; // Register this method name in lookup of known methods - registeredMethods[getKey(direction, stagger, format_as(derivType))].insert(methodName); + registeredMethods[getKey(direction, stagger, fmt::to_string(derivType))].insert(methodName); }; /// Templated versions of the above registration routines. @@ -225,7 +226,7 @@ struct DerivativeStore { DERIV derivType = DERIV::Standard) const { const auto realName = nameLookup( - name, defaultMethods.at(getKey(direction, stagger, format_as(derivType)))); + name, defaultMethods.at(getKey(direction, stagger, fmt::to_string(derivType)))); const auto key = getKey(direction, stagger, realName); const storageType* theMap = nullptr; @@ -269,7 +270,7 @@ struct DerivativeStore { DERIV derivType = DERIV::Upwind) const { const auto realName = nameLookup( - name, defaultMethods.at(getKey(direction, stagger, format_as(derivType)))); + name, defaultMethods.at(getKey(direction, stagger, fmt::to_string(derivType)))); const auto key = getKey(direction, stagger, realName); const storageType* theMap = nullptr; @@ -331,7 +332,7 @@ struct DerivativeStore { const auto theDirection = direction.first; const auto theDerivType = deriv.first; - const auto theDerivTypeString = format_as(theDerivType); + const auto theDerivTypeString = fmt::to_string(theDerivType); // Note both staggered and unstaggered have the same fallback default currently std::string theDefault{ @@ -360,7 +361,7 @@ struct DerivativeStore { defaultMethods[getKey(theDirection, STAGGER::None, theDerivTypeString)] = theDefault; output_verbose << "The default method for derivative type " << theDerivTypeString - << " in direction " << format_as(theDirection) << " is " + << " in direction " << fmt::to_string(theDirection) << " is " << theDefault << "\n"; //------------------------------------------------------------- @@ -384,7 +385,7 @@ struct DerivativeStore { defaultMethods[getKey(theDirection, STAGGER::C2L, theDerivTypeString)] = theDefault; output_verbose << "The default method for staggered derivative type " - << theDerivTypeString << " in direction " << format_as(theDirection) + << theDerivTypeString << " in direction " << fmt::to_string(theDirection) << " is " << theDefault << "\n"; } } @@ -393,7 +394,7 @@ struct DerivativeStore { /// Provide a method to override/force a specific default method void forceDefaultMethod(std::string methodName, DERIV deriv, DIRECTION direction, STAGGER stagger = STAGGER::None) { - const auto key = getKey(direction, stagger, format_as(deriv)); + const auto key = getKey(direction, stagger, fmt::to_string(deriv)); defaultMethods[key] = uppercase(methodName); } @@ -461,7 +462,7 @@ private: for (const auto& direction : directions) { for (const auto& deriv : derivTypes) { const auto theDirection = direction.first; - const auto theDerivTypeString = format_as(deriv.first); + const auto theDerivTypeString = fmt::to_string(deriv.first); const std::string theDefault{uppercase(initialDefaultMethods[deriv.first])}; //------------------------------------------------------------- @@ -482,11 +483,11 @@ private: std::string getMethodName(std::string name, DIRECTION direction, STAGGER stagger = STAGGER::None) const { - return name + " (" + format_as(direction) + ", " + format_as(stagger) + ")"; + return name + " (" + fmt::to_string(direction) + ", " + fmt::to_string(stagger) + ")"; }; std::string nameLookup(const std::string name, const std::string defaultName) const { - return name != format_as(DIFF_DEFAULT) ? name : defaultName; + return name != fmt::to_string(DIFF_DEFAULT) ? name : defaultName; } /// Provides a routine to produce a unique key given information @@ -503,8 +504,8 @@ private: // maps to store the different field types as the signature is // different. std::size_t result; - result = std::hash{}(format_as(direction)); - result = result ^ std::hash{}(format_as(stagger)); + result = std::hash{}(fmt::to_string(direction)); + result = result ^ std::hash{}(fmt::to_string(stagger)); result = result ^ std::hash{}(key); return result; } diff --git a/include/bout/difops.hxx b/include/bout/difops.hxx index 594a0d29b0..dfd1587694 100644 --- a/include/bout/difops.hxx +++ b/include/bout/difops.hxx @@ -61,7 +61,7 @@ bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline bout::FieldMetric Grad_par(const Field2D& var, CELL_LOC outloc, DIFF_METHOD method) { - return Grad_par(var, outloc, format_as(method)); + return Grad_par(var, outloc, fmt::to_string(method)); } Field3D Grad_par(const Field3DParallel& var, CELL_LOC outloc = CELL_DEFAULT, @@ -99,7 +99,7 @@ bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, inline bout::FieldMetric Vpar_Grad_par(const Field2D& v, const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Vpar_Grad_par(v, f, outloc, format_as(method)); + return Vpar_Grad_par(v, f, outloc, fmt::to_string(method)); } Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, @@ -108,7 +108,7 @@ Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, inline Field3D Vpar_Grad_par(const Field3DParallel& v, const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Vpar_Grad_par(v, f, outloc, format_as(method)); + return Vpar_Grad_par(v, f, outloc, fmt::to_string(method)); } /*! @@ -127,14 +127,14 @@ bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline bout::FieldMetric Div_par(const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Div_par(f, outloc, format_as(method)); + return Div_par(f, outloc, fmt::to_string(method)); } Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline Field3D Div_par(const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Div_par(f, outloc, format_as(method)); + return Div_par(f, outloc, fmt::to_string(method)); } // Divergence of a parallel flow: Div(f*v) @@ -151,7 +151,7 @@ Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, inline Field3D Div_par_flux(const Field3DParallel& v, const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Div_par_flux(v, f, outloc, format_as(method)); + return Div_par_flux(v, f, outloc, fmt::to_string(method)); } /*! @@ -169,13 +169,13 @@ bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline bout::FieldMetric Grad2_par2(const Field2D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Grad2_par2(f, outloc, format_as(method)); + return Grad2_par2(f, outloc, fmt::to_string(method)); } Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline Field3D Grad2_par2(const Field3DParallel& f, CELL_LOC outloc, DIFF_METHOD method) { - return Grad2_par2(f, outloc, format_as(method)); + return Grad2_par2(f, outloc, fmt::to_string(method)); } /*! diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 9eb8b3e99e..35d508c5f5 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -952,7 +952,7 @@ Field3D lowPass(const Field3D& var, int zmax, bool keep_zonal, [[deprecated("Please use a bool for `keep_zonal`")]] inline Field3D lowPass(const Field3D& var, int zmax, int keep_zonal, REGION rgn = RGN_ALL) { ASSERT0(static_cast(keep_zonal) == keep_zonal); - return lowPass(var, zmax, static_cast(keep_zonal), format_as(rgn)); + return lowPass(var, zmax, static_cast(keep_zonal), fmt::to_string(rgn)); } /// Fourier low pass filtering. Removes modes higher than \p zmax diff --git a/include/bout/index_derivs.hxx b/include/bout/index_derivs.hxx index 9677d1597e..2127452f4e 100644 --- a/include/bout/index_derivs.hxx +++ b/include/bout/index_derivs.hxx @@ -68,7 +68,7 @@ inline std::ostream& operator<<(std::ostream& out, const metaData& meta) { out << ", "; out << "nGuards : " << meta.nGuards; out << ", "; - out << "type : " << format_as(meta.derivType); + out << "type : " << fmt::to_string(meta.derivType); return out; } diff --git a/include/bout/interpolation.hxx b/include/bout/interpolation.hxx index 6bcffce8cc..3ec77994a2 100644 --- a/include/bout/interpolation.hxx +++ b/include/bout/interpolation.hxx @@ -88,7 +88,7 @@ interp_to(const T& var, CELL_LOC loc, const std::string& region = "RGN_ALL") { T result{emptyFrom(var).setLocation(loc)}; // Staggered grids enabled, and need to perform interpolation - TRACE("Interpolating {} -> {}", format_as(var.getLocation()), format_as(loc)); + TRACE("Interpolating {} -> {}", fmt::to_string(var.getLocation()), fmt::to_string(loc)); if (region != "RGN_NOBNDRY") { // result is requested in some boundary region(s) diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 151b24042b..f8fb83cca4 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -567,10 +567,6 @@ inline std::string format_as(const bool& val) { return "false"; } -/// Convert a time stamp to a string -/// This uses std::localtime and std::put_time -std::string format_as(const time_t& time); - /*! * Convert a string to lower case */ diff --git a/include/bout/vecops.hxx b/include/bout/vecops.hxx index 0c0c105326..60985ec80e 100644 --- a/include/bout/vecops.hxx +++ b/include/bout/vecops.hxx @@ -64,7 +64,7 @@ Vector3D Grad(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, Vector3D Grad_perp(const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); inline Vector3D Grad_perp(const Field3D& f, CELL_LOC outloc, DIFF_METHOD method) { - return Grad_perp(f, outloc, format_as(method)); + return Grad_perp(f, outloc, fmt::to_string(method)); } Vector2D Grad_perp(const Field2D& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT"); @@ -92,7 +92,7 @@ Field3D Div(const Vector3D& v, const Field3D& f, CELL_LOC outloc = CELL_DEFAULT, inline Field3D Div(const Vector3D& v, const Field3D& f, CELL_LOC outloc, DIFF_METHOD method = DIFF_DEFAULT) { - return Div(v, f, outloc, format_as(method)); + return Div(v, f, outloc, fmt::to_string(method)); } /// Curl of a vector diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index e48c15e274..a15bed1b4c 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -119,7 +119,7 @@ Field2D* Field2D::timeDeriv() { ////////////// Indexing /////////////////// const Region& Field2D::getRegion(REGION region) const { - return fieldmesh->getRegion2D(format_as(region)); + return fieldmesh->getRegion2D(fmt::to_string(region)); } const Region& Field2D::getRegion(const std::string& region_name) const { return fieldmesh->getRegion2D(region_name); diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index c83c0cd36e..f8eaec9814 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -264,14 +264,14 @@ const BoutReal& Field3D::operator()(const Ind2D& d, int jz) const { } const Region& Field3D::getRegion(REGION region) const { - return fieldmesh->getRegion3D(format_as(region)); + return fieldmesh->getRegion3D(fmt::to_string(region)); } const Region& Field3D::getRegion(const std::string& region_name) const { return fieldmesh->getRegion3D(region_name); } const Region& Field3D::getRegion2D(REGION region) const { - return fieldmesh->getRegion2D(format_as(region)); + return fieldmesh->getRegion2D(fmt::to_string(region)); } const Region& Field3D::getRegion2D(const std::string& region_name) const { return fieldmesh->getRegion2D(region_name); @@ -715,7 +715,7 @@ Field3D filter(const Field3D& var, int N0, const std::string& rgn) { Field3D result{emptyFrom(var)}; - const auto region_str = format_as(rgn); + const auto region_str = fmt::to_string(rgn); // Only allow a whitelist of regions for now ASSERT2(region_str == "RGN_ALL" || region_str == "RGN_NOBNDRY" @@ -765,7 +765,7 @@ Field3D lowPass(const Field3D& var, int zmax, bool keep_zonal, const std::string Field3D result{emptyFrom(var)}; - const auto region_str = format_as(rgn); + const auto region_str = fmt::to_string(rgn); // Only allow a whitelist of regions for now ASSERT2(region_str == "RGN_ALL" || region_str == "RGN_NOBNDRY" @@ -829,7 +829,7 @@ void shiftZ(Field3D& var, int jx, int jy, double zangle) { } void shiftZ(Field3D& var, double zangle, const std::string& rgn) { - const auto region_str = format_as(rgn); + const auto region_str = fmt::to_string(rgn); // Only allow a whitelist of regions for now ASSERT2(region_str == "RGN_ALL" || region_str == "RGN_NOBNDRY" diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index 24e966093c..99ba1d9a35 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -107,7 +107,7 @@ FieldPerp& FieldPerp::operator=(const BoutReal rhs) { } const Region& FieldPerp::getRegion(REGION region) const { - return fieldmesh->getRegionPerp(format_as(region)); + return fieldmesh->getRegionPerp(fmt::to_string(region)); } const Region& FieldPerp::getRegion(const std::string& region_name) const { return fieldmesh->getRegionPerp(region_name); diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx index 5c3d5c6350..5b45d097d5 100644 --- a/src/mesh/coordinates.cxx +++ b/src/mesh/coordinates.cxx @@ -624,7 +624,7 @@ void Coordinates::readParallelMetricComponents() { void Coordinates::outputVars(Options& output_options) { const Timer time("io"); const std::string loc_string = - (location == CELL_CENTRE) ? "" : "_" + format_as(location); + (location == CELL_CENTRE) ? "" : "_" + fmt::to_string(location); output_options["dx" + loc_string].force(dx(), "Coordinates"); output_options["dy" + loc_string].force(dy(), "Coordinates"); diff --git a/src/mesh/interpolation_xz.cxx b/src/mesh/interpolation_xz.cxx index 1a1ec67497..c95c161db4 100644 --- a/src/mesh/interpolation_xz.cxx +++ b/src/mesh/interpolation_xz.cxx @@ -35,8 +35,8 @@ #include #include -void printLocation(const Field3D& var) { output << format_as(var.getLocation()); } -void printLocation(const Field2D& var) { output << format_as(var.getLocation()); } +void printLocation(const Field3D& var) { output << fmt::to_string(var.getLocation()); } +void printLocation(const Field2D& var) { output << fmt::to_string(var.getLocation()); } const Field3D interpolate(const Field3D& f, const Field3D& delta_x, const Field3D& delta_z) { diff --git a/src/mesh/parallel/shiftedmetric.cxx b/src/mesh/parallel/shiftedmetric.cxx index 9036b2e697..18e993e285 100644 --- a/src/mesh/parallel/shiftedmetric.cxx +++ b/src/mesh/parallel/shiftedmetric.cxx @@ -48,7 +48,7 @@ void ShiftedMetric::checkInputGrid() { void ShiftedMetric::outputVars(Options& output_options) { Timer time("io"); const std::string loc_string = - (location == CELL_CENTRE) ? "" : "_" + format_as(location); + (location == CELL_CENTRE) ? "" : "_" + fmt::to_string(location); output_options["zShift" + loc_string].force(zShift, "ShiftedMetric"); } diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index 1cfccfadf0..f6dcb23c5a 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -281,7 +281,7 @@ void Solver::add(Field3D& v, const std::string& name, const std::string& descrip if (mesh->StaggerGrids && (v.getLocation() != CELL_CENTRE)) { output_info.write("\tVariable {:s} shifted to {:s}\n", name, - format_as(v.getLocation())); + fmt::to_string(v.getLocation())); ddt(v).setLocation(v.getLocation()); // Make sure both at the same location } @@ -605,7 +605,7 @@ int Solver::solve(int nout, BoutReal timestep) { } time_t start_time = time(nullptr); - output_progress.write(_f("\nRun started at : {:s}\n"), format_as(start_time)); + output_progress.write(_f("\nRun started at : {:s}\n"), fmt::to_string(start_time)); Timer timer("run"); // Start timer @@ -651,7 +651,7 @@ int Solver::solve(int nout, BoutReal timestep) { status = run(); time_t end_time = time(nullptr); - output_progress.write(_f("\nRun finished at : {:s}\n"), format_as(end_time)); + output_progress.write(_f("\nRun finished at : {:s}\n"), fmt::to_string(end_time)); output_progress.write(_("Run time : ")); int dt = end_time - start_time; @@ -1274,7 +1274,7 @@ std::vector Solver::getJacobianMetadata2D() co metadata.reserve(f2d.size()); for (int i = 0; i < static_cast(f2d.size()); ++i) { - metadata.push_back(JacobianVariableMetadata{i, f2d[i].name, format_as(f2d[i].location), + metadata.push_back(JacobianVariableMetadata{i, f2d[i].name, fmt::to_string(f2d[i].location), f2d[i].evolve_bndry, f2d[i].constraint, f2d[i].description}); } @@ -1287,7 +1287,7 @@ std::vector Solver::getJacobianMetadata3D() co metadata.reserve(f3d.size()); for (int i = 0; i < static_cast(f3d.size()); ++i) { - metadata.push_back(JacobianVariableMetadata{i, f3d[i].name, format_as(f3d[i].location), + metadata.push_back(JacobianVariableMetadata{i, f3d[i].name, fmt::to_string(f3d[i].location), f3d[i].evolve_bndry, f3d[i].constraint, f3d[i].description}); } @@ -1390,7 +1390,7 @@ std::string Solver::getJacobianMatrixFilename(const std::string& jacobian_export // The directory the output data is stored in const std::string datadir = Options::root()["datadir"]; const std::string stem = fmt::format("{}/{}_{}_{:06d}", datadir, jacobian_export_prefix, - format_as(kind), this->jacobian_export_counter++); + fmt::to_string(kind), this->jacobian_export_counter++); return stem + (format == bout::PetscMatrixExportFormat::binary ? ".dat" : ".txt"); } diff --git a/src/sys/bout_types.cxx b/src/sys/bout_types.cxx index 9c43a104b1..ab95ef07c0 100644 --- a/src/sys/bout_types.cxx +++ b/src/sys/bout_types.cxx @@ -74,7 +74,7 @@ std::string format_as(const DIRECTION& direction) { } std::string format_as(const DirectionTypes& dir) { - return "{" + format_as(dir.y) + ", " + format_as(dir.z) + "}"; + return "{" + fmt::to_string(dir.y) + ", " + fmt::to_string(dir.z) + "}"; } void swap(DirectionTypes& first, DirectionTypes& second) { diff --git a/src/sys/options.cxx b/src/sys/options.cxx index 0cdddc215b..1503d6200d 100644 --- a/src/sys/options.cxx +++ b/src/sys/options.cxx @@ -312,25 +312,25 @@ bool Options::isSection(const std::string& name) const { template <> Options& Options::assign<>(Field2D val, std::string source) { - attributes["cell_location"] = format_as(val.getLocation()); - attributes["direction_y"] = format_as(val.getDirectionY()); - attributes["direction_z"] = format_as(val.getDirectionZ()); + attributes["cell_location"] = fmt::to_string(val.getLocation()); + attributes["direction_y"] = fmt::to_string(val.getDirectionY()); + attributes["direction_z"] = fmt::to_string(val.getDirectionZ()); _set_no_check(std::move(val), std::move(source)); return *this; } template <> Options& Options::assign<>(Field3D val, std::string source) { - attributes["cell_location"] = format_as(val.getLocation()); - attributes["direction_y"] = format_as(val.getDirectionY()); - attributes["direction_z"] = format_as(val.getDirectionZ()); + attributes["cell_location"] = fmt::to_string(val.getLocation()); + attributes["direction_y"] = fmt::to_string(val.getDirectionY()); + attributes["direction_z"] = fmt::to_string(val.getDirectionZ()); _set_no_check(std::move(val), std::move(source)); return *this; } template <> Options& Options::assign<>(FieldPerp val, std::string source) { - attributes["cell_location"] = format_as(val.getLocation()); - attributes["direction_y"] = format_as(val.getDirectionY()); - attributes["direction_z"] = format_as(val.getDirectionZ()); + attributes["cell_location"] = fmt::to_string(val.getLocation()); + attributes["direction_y"] = fmt::to_string(val.getDirectionY()); + attributes["direction_z"] = fmt::to_string(val.getDirectionZ()); attributes["yindex_global"] = val.getGlobalIndex(); _set_no_check(std::move(val), std::move(source)); return *this; @@ -542,7 +542,7 @@ bool Options::as(const bool& UNUSED(similar_to)) const { value_used = true; - printNameValueSourceLine(*this, format_as(result)); + printNameValueSourceLine(*this, fmt::to_string(result)); return result; } diff --git a/tests/integrated/test-fci-boundary/get_par_bndry.cxx b/tests/integrated/test-fci-boundary/get_par_bndry.cxx index 8d2d5a3315..1954496e5b 100644 --- a/tests/integrated/test-fci-boundary/get_par_bndry.cxx +++ b/tests/integrated/test-fci-boundary/get_par_bndry.cxx @@ -22,7 +22,7 @@ int main(int argc, char** argv) { for (int i = 0; i < fields.size(); i++) { fields[i].allocate(); const auto boundary = static_cast(i); - const auto boundary_name = format_as(boundary); + const auto boundary_name = fmt::to_string(boundary); mesh->communicate(fields[i]); for (auto& bndry_par : mesh->getBoundariesPar(boundary)) { output.write("{:s} region\n", boundary_name); diff --git a/tests/integrated/test-invpar/test_invpar.cxx b/tests/integrated/test-invpar/test_invpar.cxx index 5ac808efc9..ce50110c0e 100644 --- a/tests/integrated/test-invpar/test_invpar.cxx +++ b/tests/integrated/test-invpar/test_invpar.cxx @@ -16,7 +16,7 @@ int test(const std::string& acoef, const std::string& bcoef, const std::string& BoutReal tol, FieldFactory& f, CELL_LOC location) { output_error.write( "acoef={:s} bcoef={:s} ccoef={:s} dcoef={:s} ecoef{:s} with {:s} at {:s} ...", - acoef, bcoef, ccoef, dcoef, ecoef, func, format_as(location)); + acoef, bcoef, ccoef, dcoef, ecoef, func, fmt::to_string(location)); auto inv = InvertPar::create(nullptr, location, mesh); Field2D A = f.create2D(acoef, nullptr, nullptr, location); diff --git a/tests/unit/include/bout/test_bout_enum_class.cxx b/tests/unit/include/bout/test_bout_enum_class.cxx index 009939a2b3..7b22388c67 100644 --- a/tests/unit/include/bout/test_bout_enum_class.cxx +++ b/tests/unit/include/bout/test_bout_enum_class.cxx @@ -1,3 +1,4 @@ +#include "fmt/format.h" #include #include "bout/bout_enum_class.hxx" @@ -10,13 +11,13 @@ BOUT_ENUM_CLASS_NS(bout, TestEnum, foo, bar); BOUT_ENUM_CLASS_NS(bout::test, TestEnum, foo, bar); TEST(BoutEnumClassNS, toString) { - EXPECT_EQ(format_as(bout::TestEnum::foo), "foo"); - EXPECT_EQ(format_as(bout::TestEnum::bar), "bar"); + EXPECT_EQ(fmt::to_string(bout::TestEnum::foo), "foo"); + EXPECT_EQ(fmt::to_string(bout::TestEnum::bar), "bar"); } TEST(BoutEnumClass, toString) { - EXPECT_EQ(format_as(TestEnum::foo), "foo"); - EXPECT_EQ(format_as(TestEnum::bar), "bar"); + EXPECT_EQ(fmt::to_string(TestEnum::foo), "foo"); + EXPECT_EQ(fmt::to_string(TestEnum::bar), "bar"); } TEST(BoutEnumClassNS, fromString) { diff --git a/tests/unit/sys/test_options_netcdf.cxx b/tests/unit/sys/test_options_netcdf.cxx index 5de1087ed4..e30f286e10 100644 --- a/tests/unit/sys/test_options_netcdf.cxx +++ b/tests/unit/sys/test_options_netcdf.cxx @@ -189,7 +189,7 @@ TEST_F(OptionsNetCDFTest, Field2DWriteCellCentre) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), - format_as(CELL_CENTRE)); + fmt::to_string(CELL_CENTRE)); } TEST_F(OptionsNetCDFTest, Field2DWriteCellYLow) { @@ -205,7 +205,7 @@ TEST_F(OptionsNetCDFTest, Field2DWriteCellYLow) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), - format_as(CELL_YLOW)); + fmt::to_string(CELL_YLOW)); } TEST_F(OptionsNetCDFTest, Field3DWriteCellCentre) { @@ -221,7 +221,7 @@ TEST_F(OptionsNetCDFTest, Field3DWriteCellCentre) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - format_as(CELL_CENTRE)); + fmt::to_string(CELL_CENTRE)); } TEST_F(OptionsNetCDFTest, Field3DWriteCellYLow) { @@ -237,7 +237,7 @@ TEST_F(OptionsNetCDFTest, Field3DWriteCellYLow) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - format_as(CELL_YLOW)); + fmt::to_string(CELL_YLOW)); } TEST_F(OptionsNetCDFTest, FieldPerpWriteCellCentre) { @@ -259,7 +259,7 @@ TEST_F(OptionsNetCDFTest, FieldPerpWriteCellCentre) { Options data = OptionsIO::create(filename)->read(); EXPECT_EQ(data["fperp"].attributes["cell_location"].as(), - format_as(CELL_CENTRE)); + fmt::to_string(CELL_CENTRE)); EXPECT_EQ(data["fperp"].attributes["yindex_global"].as(), 2); } diff --git a/tests/unit/sys/test_utils.cxx b/tests/unit/sys/test_utils.cxx index 22b27a4e7f..19a1cc4f5e 100644 --- a/tests/unit/sys/test_utils.cxx +++ b/tests/unit/sys/test_utils.cxx @@ -1,3 +1,4 @@ +#include "fmt/format.h" #include "bout/utils.hxx" #include "gtest/gtest.h" @@ -527,18 +528,18 @@ TEST(StringUtilitiesTest, BoolToString) { std::string true_string = "true"; std::string false_string = "false"; - EXPECT_EQ(true_string, format_as(true)); - EXPECT_EQ(false_string, format_as(false)); + EXPECT_EQ(true_string, fmt::to_string(true)); + EXPECT_EQ(false_string, fmt::to_string(false)); } TEST(StringUtilitiesTest, ConstCharToString) { - EXPECT_EQ(std::string("hello"), format_as("hello")); + EXPECT_EQ(std::string("hello"), fmt::to_string("hello")); } TEST(StringUtilitiesTest, StringToString) { std::string test_string = "dlkjl872kj"; - EXPECT_EQ(test_string, format_as(test_string)); + EXPECT_EQ(test_string, fmt::to_string(test_string)); } TEST(StringUtilitiesTest, IntToString) { From eaa3a6ac6a553a191db81a5d4c9932b4f3b05ceb Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 13 Oct 2026 12:16:54 +0100 Subject: [PATCH 18/37] Replace `{:s}` with `{}` Type-specific format specifiers not always supported now that `toString()` is not being explicitly called. --- include/bout/options.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index a2389232fd..51f10d9b51 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -665,7 +665,7 @@ public: if (bout::utils::variantEqualTo(attributes.at("source"), DEFAULT_SOURCE)) { // Check that the default values are the same if (!similar(val, def)) { - throw BoutException("Inconsistent default values for '{:s}': '{:s}' then '{:s}'", + throw BoutException("Inconsistent default values for '{}': '{}' then '{}'", full_name, bout::utils::variantToString(value), def); } From 36826242f91f1842d611808f731e5628d50d9979 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 13 Oct 2026 12:18:44 +0100 Subject: [PATCH 19/37] Remove namespace and qualify class name --- include/bout/output_bout_types.hxx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/bout/output_bout_types.hxx b/include/bout/output_bout_types.hxx index c606eaf265..2bdef607bb 100644 --- a/include/bout/output_bout_types.hxx +++ b/include/bout/output_bout_types.hxx @@ -115,10 +115,9 @@ constexpr auto parse_nonnegative_int(const char*& begin, const char* end, /// - ``#``: Plot slices as 2D heatmap /// - ``e``: Number of elements at each edge to show /// - ``f``: Show full field -namespace fmt { - template -struct formatter>>> { +struct fmt::formatter>>> { private: formatter underlying; @@ -284,6 +283,4 @@ public: } }; -} // namespace fmt - #endif // OUTPUT_BOUT_TYPES_H From 7fe4256fdc99c343013af7be253e01d55a5603cc Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 13 Oct 2026 12:20:13 +0100 Subject: [PATCH 20/37] Remove `format_as()` functions for std::string and bool types --- include/bout/utils.hxx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index f8fb83cca4..f581a1fbc1 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -523,11 +523,6 @@ inline void checkData(BoutReal UNUSED(f)) {}; */ char* copy_string(const char* s); -/// Simple case where input is already a string -/// This is so that format_as can be used in templates -/// where the type may be std::string. -inline std::string format_as(const std::string& val) { return val; } - template inline std::string format_as(const Array& UNUSED(val)) { return ""; @@ -559,14 +554,6 @@ struct fmt::range_format_kind, Char> { static constexpr auto value = fmt::range_format::disabled; }; -/// Convert a bool to "true" or "false" -inline std::string format_as(const bool& val) { - if (val) { - return "true"; - } - return "false"; -} - /*! * Convert a string to lower case */ From 18e1b25af7de4ed55df7aab5964aca71798d8350 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 13 Oct 2026 12:21:27 +0100 Subject: [PATCH 21/37] Minor tidying: Include headers directly Reorder various things Whitespace changes --- include/bout/bout_enum_class.hxx | 1 + include/bout/bout_types.hxx | 4 ++-- include/bout/field2d.hxx | 6 +++++- include/bout/sys/variant.hxx | 1 + src/field/field2d.cxx | 1 + src/sys/bout_types.cxx | 1 + tests/unit/field/test_fieldperp.cxx | 1 + 7 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/bout/bout_enum_class.hxx b/include/bout/bout_enum_class.hxx index 50e0b7f8d0..a50cc56940 100644 --- a/include/bout/bout_enum_class.hxx +++ b/include/bout/bout_enum_class.hxx @@ -112,6 +112,7 @@ enum class enumname : std::uint8_t { __VA_ARGS__ }; \ \ inline std::string format_as(const enumname& e) { \ + \ const static std::map toString_map = { \ BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, enumname, __VA_ARGS__)}; \ auto found = toString_map.find(e); \ diff --git a/include/bout/bout_types.hxx b/include/bout/bout_types.hxx index a60edf02bf..4cb51cbe37 100644 --- a/include/bout/bout_types.hxx +++ b/include/bout/bout_types.hxx @@ -95,8 +95,8 @@ std::string format_as(const DIRECTION& dir); /// coordinates enum class YDirectionType { Standard, Aligned }; -YDirectionType YDirectionTypeFromString(const std::string& y_direction_string); std::string format_as(const YDirectionType& d); +YDirectionType YDirectionTypeFromString(const std::string& y_direction_string); /// Identify kind of a field's z-direction /// - Standard is the default @@ -105,8 +105,8 @@ std::string format_as(const YDirectionType& d); /// Field2D) enum class ZDirectionType { Standard, Average }; -ZDirectionType ZDirectionTypeFromString(const std::string& z_direction_string); std::string format_as(const ZDirectionType& d); +ZDirectionType ZDirectionTypeFromString(const std::string& z_direction_string); /// Container for direction types struct DirectionTypes { diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index e5a4368f0b..bbd23e6bc9 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -24,11 +24,14 @@ * along with BOUT++. If not, see . * */ + +#pragma once + #include "bout/utils.hxx" #include + class Field2D; -#pragma once #ifndef BOUT_FIELD2D_H #define BOUT_FIELD2D_H @@ -46,6 +49,7 @@ class Field2D; #include #include #include +#include "fmt/format.h" #include #include diff --git a/include/bout/sys/variant.hxx b/include/bout/sys/variant.hxx index a6c2307a0a..b17b73ffb3 100644 --- a/include/bout/sys/variant.hxx +++ b/include/bout/sys/variant.hxx @@ -15,6 +15,7 @@ #pragma once +#include #ifndef __VARIANT_HXX__ #define __VARIANT_HXX__ diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index a15bed1b4c..8727bc0cd9 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -25,6 +25,7 @@ * **************************************************************************/ +#include "fmt/format.h" #include "bout/bout_types.hxx" #include "bout/build_defines.hxx" diff --git a/src/sys/bout_types.cxx b/src/sys/bout_types.cxx index ab95ef07c0..aab1bc5491 100644 --- a/src/sys/bout_types.cxx +++ b/src/sys/bout_types.cxx @@ -1,3 +1,4 @@ +#include "fmt/format.h" #include #include diff --git a/tests/unit/field/test_fieldperp.cxx b/tests/unit/field/test_fieldperp.cxx index e0e0f85850..6bd8997dfc 100644 --- a/tests/unit/field/test_fieldperp.cxx +++ b/tests/unit/field/test_fieldperp.cxx @@ -12,6 +12,7 @@ #include "bout/output.hxx" #include "bout/unused.hxx" #include "bout/utils.hxx" +#include "fmt/format.h" #include #include From 8fb5952f466512f8e041d0a3558d2af809301ef4 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 14 Oct 2026 22:23:42 +0100 Subject: [PATCH 22/37] Restore `toString()` for fields --- include/bout/field2d.hxx | 8 ++++---- include/bout/field3d.hxx | 8 ++++---- include/bout/fieldperp.hxx | 8 ++++---- include/bout/options.hxx | 2 +- include/bout/sys/variant.hxx | 2 +- include/bout/utils.hxx | 15 ++++++++++++--- src/field/field2d.cxx | 3 ++- src/field/field3d.cxx | 3 ++- src/field/fieldperp.cxx | 2 +- tests/unit/field/test_fieldperp.cxx | 4 ++-- 10 files changed, 33 insertions(+), 22 deletions(-) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index bbd23e6bc9..353dba1460 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -612,12 +612,12 @@ inline Field2D& ddt(Field2D& f) { return *(f.timeDeriv()); } /// Alias for ddt that is more readable for constraints inline Field2D& residual(Field2D& f) { return *(f.timeDeriv()); } +/// toString template specialisation +/// Defined in utils.hxx template <> -struct fmt::formatter : fmt::formatter { - auto format(const Field2D&, format_context& ctx) const { - return fmt::formatter::format("", ctx); +inline std::string toString<>([[maybe_unused]] const Field2D& val) { + return ""; } -}; /// Test if two fields are the same, by calculating /// the minimum absolute difference between them diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 35d508c5f5..b448988c04 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1000,12 +1000,12 @@ inline Field3D& ddt(Field3D& f) { return *(f.timeDeriv()); } /// Alias for ddt that is more readable for constraints inline Field3D& residual(Field3D& f) { return *(f.timeDeriv()); } +/// toString template specialisation +/// Defined in utils.hxx template <> -struct fmt::formatter : fmt::formatter { - auto format(const Field3D&, format_context& ctx) const { - return fmt::formatter::format("", ctx); +inline std::string toString<>([[maybe_unused]] const Field3D& val) { + return ""; } -}; /// Test if two fields are the same, by calculating /// the minimum absolute difference between them diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 430cb185a9..19527d9052 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -461,12 +461,12 @@ void invalidateGuards(FieldPerp& var); inline void invalidateGuards(FieldPerp& UNUSED(var)) {} #endif +/// toString template specialisation +/// Defined in utils.hxx template <> -struct fmt::formatter : fmt::formatter { - auto format(const FieldPerp&, format_context& ctx) const { - return fmt::formatter::format("", ctx); +inline std::string toString<>([[maybe_unused]] const FieldPerp& val) { + return ""; } -}; /// Test if two fields are the same, by checking that they are defined /// at the same y-index, and if the minimum absolute difference diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 51f10d9b51..cadf695069 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -1027,7 +1027,7 @@ void saveParallel(Options& opt, const std::string& name, const Field3D& tosave); /// convertible to an `Options`, and we want _exactly_ `Options` template > inline std::ostream& operator<<(std::ostream& out, const T& value) { - return out << fmt::to_string(value); + return out << toString(value); } namespace bout { diff --git a/include/bout/sys/variant.hxx b/include/bout/sys/variant.hxx index b17b73ffb3..f43c9b5926 100644 --- a/include/bout/sys/variant.hxx +++ b/include/bout/sys/variant.hxx @@ -146,7 +146,7 @@ namespace details { struct ToString { template std::string operator()(T&& val) { - return fmt::to_string(std::forward(val)); + return toString(std::forward(val)); } }; diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index f581a1fbc1..0f034f4319 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -524,17 +524,17 @@ inline void checkData(BoutReal UNUSED(f)) {}; char* copy_string(const char* s); template -inline std::string format_as(const Array& UNUSED(val)) { +inline std::string toString(const Array& UNUSED(val)) { return ""; } template -inline std::string format_as(const Matrix& UNUSED(val)) { +inline std::string toString(const Matrix& UNUSED(val)) { return ""; } template -inline std::string format_as(const Tensor& UNUSED(val)) { +inline std::string toString(const Tensor& UNUSED(val)) { return ""; } @@ -554,6 +554,15 @@ struct fmt::range_format_kind, Char> { static constexpr auto value = fmt::range_format::disabled; }; +/// Convert a value to a string +/// by writing to a stringstream +template +std::string toString(const T& val) { + std::stringstream ss; + ss << val; + return ss.str(); +} + /*! * Convert a string to lower case */ diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index 8727bc0cd9..dca47b8dc6 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -44,6 +44,7 @@ #include #include #include +#include Field2D::Field2D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in, [[maybe_unused]] std::optional regionID) @@ -381,7 +382,7 @@ bool operator==(const Field2D& a, const Field2D& b) { } std::ostream& operator<<(std::ostream& out, const Field2D& value) { - out << fmt::to_string(value); + out << toString(value); return out; } diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index f8eaec9814..34a8594bc5 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -906,7 +907,7 @@ bool operator==(const Field3D& a, const Field3D& b) { } std::ostream& operator<<(std::ostream& out, const Field3D& value) { - out << fmt::to_string(value); + out << toString(value); return out; } diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index 99ba1d9a35..cb0bfe6883 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -245,7 +245,7 @@ bool operator==(const FieldPerp& a, const FieldPerp& b) { } std::ostream& operator<<(std::ostream& out, const FieldPerp& value) { - out << fmt::to_string(value); + out << toString(value); return out; } diff --git a/tests/unit/field/test_fieldperp.cxx b/tests/unit/field/test_fieldperp.cxx index 6bd8997dfc..1a04d6cf6a 100644 --- a/tests/unit/field/test_fieldperp.cxx +++ b/tests/unit/field/test_fieldperp.cxx @@ -1876,7 +1876,7 @@ TEST_F(FieldPerpTest, ToString) { // Just check we can call toString const std::string expected = ""; const FieldPerp field{}; - EXPECT_EQ(fmt::to_string(field), expected); + EXPECT_EQ(toString(field), expected); } TEST_F(FieldPerpTest, OperatorStream) { @@ -1884,7 +1884,7 @@ TEST_F(FieldPerpTest, OperatorStream) { const FieldPerp field{}; std::stringstream stream; stream << field; - EXPECT_EQ(stream.str(), fmt::to_string(field)); + EXPECT_EQ(stream.str(), toString(field)); } TEST_F(FieldPerpTest, Equality) { From f1ee6859da557707ae8dda2769998b7e6e64842f Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 6 Oct 2026 17:13:52 +0100 Subject: [PATCH 23/37] Disable the range formatter for Field types so that {fmt} will not treat them as ranges so that it uses `format_as()`` instead. --- include/bout/field2d.hxx | 3 +++ include/bout/field3d.hxx | 3 +++ include/bout/fieldperp.hxx | 3 +++ 3 files changed, 9 insertions(+) diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 353dba1460..1ad1f5bc06 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -625,4 +625,7 @@ bool operator==(const Field2D& a, const Field2D& b); std::ostream& operator<<(std::ostream& out, const Field2D& value); +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template <> struct fmt::is_range : std::false_type {}; + #endif /* BOUT_FIELD2D_H */ diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index b448988c04..2b7c8e7ec6 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1239,4 +1239,7 @@ filledFrom(const Field3DParallel& f, return result; } +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template <> struct fmt::is_range : std::false_type {}; + #endif /* BOUT_FIELD3D_H */ diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 19527d9052..4a60c48046 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -485,4 +485,7 @@ struct is_expr_fieldperp> || (is_expr_fieldperp_v> && is_expr_constant_v>)> {}; +// Tell {fmt} not to treat these as ranges so it uses format_as() instead +template <> struct fmt::is_range : std::false_type {}; + #endif From 62f2b3b36236e7f92cc7c760a332b37785e3b3fb Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 10:58:49 +0100 Subject: [PATCH 24/37] Fix signature of `format_as()` for DIFF_METHOD Make it a const ref to match the definition in the header. --- src/sys/bout_types.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/bout_types.cxx b/src/sys/bout_types.cxx index aab1bc5491..817068de76 100644 --- a/src/sys/bout_types.cxx +++ b/src/sys/bout_types.cxx @@ -44,7 +44,7 @@ CELL_LOC CELL_LOCFromString(const std::string& location_string) { return safeAt(stringtoCELL_LOC, location_string); } -std::string format_as(DIFF_METHOD method) { +std::string format_as(const DIFF_METHOD& method) { const static std::map DIFF_METHODtoString = { {DIFF_DEFAULT, "DEFAULT"}, {DIFF_U1, "U1"}, {DIFF_U2, "U2"}, {DIFF_U3, "U3"}, From b36200fb3bf121ec3192bee0d212c45209bbef2e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 11:03:54 +0100 Subject: [PATCH 25/37] Apply clang-format --- include/bout/bout_enum_class.hxx | 76 ++++++++++++++--------------- include/bout/deriv_store.hxx | 10 ++-- include/bout/field2d.hxx | 7 +-- include/bout/field3d.hxx | 5 +- include/bout/fieldperp.hxx | 5 +- include/bout/options.hxx | 10 ++-- src/solver/solver.cxx | 17 ++++--- tests/unit/field/test_fieldperp.cxx | 2 +- tests/unit/sys/test_utils.cxx | 6 +-- 9 files changed, 71 insertions(+), 67 deletions(-) diff --git a/include/bout/bout_enum_class.hxx b/include/bout/bout_enum_class.hxx index a50cc56940..9d4249213d 100644 --- a/include/bout/bout_enum_class.hxx +++ b/include/bout/bout_enum_class.hxx @@ -107,44 +107,44 @@ /// Create an enum class with format_as and FromString functions, and an /// Options::as overload to read the enum -#define BOUT_ENUM_CLASS_NS(ns, enumname, ...) \ - namespace ns { \ - enum class enumname : std::uint8_t { __VA_ARGS__ }; \ - \ - inline std::string format_as(const enumname& e) { \ - \ - const static std::map toString_map = { \ - BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, enumname, __VA_ARGS__)}; \ - auto found = toString_map.find(e); \ - if (found == toString_map.end()) { \ - throw BoutException("Did not find enum {:d}", static_cast(e)); \ - } \ - return found->second; \ - } \ - \ - inline enumname BOUT_MAKE_FROMSTRING_NAME(enumname)(const std::string& s) { \ - const static std::map fromString_map = { \ - BOUT_ENUM_CLASS_MAP_ARGS(BOUT_STR_ENUM_CLASS, enumname, __VA_ARGS__)}; \ - auto found = fromString_map.find(s); \ - if (found == fromString_map.end()) { \ - std::string valid_values{}; \ - for (auto const& entry : fromString_map) { \ - valid_values += std::string(" ") + entry.first; \ - } \ - throw BoutException("Did not find enum {:s}. Valid values: {:s}", s, \ - valid_values); \ - } \ - return found->second; \ - } \ - \ - inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ - return out << fmt::to_string(e); \ - } \ - } /* end namespace ns */ \ - \ - template <> \ - inline ns::enumname Options::as(const ns::enumname&) const { \ - return ns::BOUT_MAKE_FROMSTRING_NAME(enumname)(this->as()); \ +#define BOUT_ENUM_CLASS_NS(ns, enumname, ...) \ + namespace ns { \ + enum class enumname : std::uint8_t { __VA_ARGS__ }; \ + \ + inline std::string format_as(const enumname& e) { \ + \ + const static std::map toString_map = { \ + BOUT_ENUM_CLASS_MAP_ARGS(BOUT_ENUM_CLASS_STR, enumname, __VA_ARGS__)}; \ + auto found = toString_map.find(e); \ + if (found == toString_map.end()) { \ + throw BoutException("Did not find enum {:d}", static_cast(e)); \ + } \ + return found->second; \ + } \ + \ + inline enumname BOUT_MAKE_FROMSTRING_NAME(enumname)(const std::string& s) { \ + const static std::map fromString_map = { \ + BOUT_ENUM_CLASS_MAP_ARGS(BOUT_STR_ENUM_CLASS, enumname, __VA_ARGS__)}; \ + auto found = fromString_map.find(s); \ + if (found == fromString_map.end()) { \ + std::string valid_values{}; \ + for (auto const& entry : fromString_map) { \ + valid_values += std::string(" ") + entry.first; \ + } \ + throw BoutException("Did not find enum {:s}. Valid values: {:s}", s, \ + valid_values); \ + } \ + return found->second; \ + } \ + \ + inline std::ostream& operator<<(std::ostream& out, const enumname& e) { \ + return out << fmt::to_string(e); \ + } \ + } /* end namespace ns */ \ + \ + template <> \ + inline ns::enumname Options::as(const ns::enumname&) const { \ + return ns::BOUT_MAKE_FROMSTRING_NAME(enumname)(this->as()); \ } #endif // BOUT_ENUM_CLASS_H diff --git a/include/bout/deriv_store.hxx b/include/bout/deriv_store.hxx index 885cc12b5d..51d3de65f4 100644 --- a/include/bout/deriv_store.hxx +++ b/include/bout/deriv_store.hxx @@ -163,7 +163,8 @@ struct DerivativeStore { }; // Register this method name in lookup of known methods - registeredMethods[getKey(direction, stagger, fmt::to_string(derivType))].insert(methodName); + registeredMethods[getKey(direction, stagger, fmt::to_string(derivType))].insert( + methodName); }; /// Register a function with upwindFunc/fluxFunc interface. Which map is used @@ -197,7 +198,8 @@ struct DerivativeStore { }; // Register this method name in lookup of known methods - registeredMethods[getKey(direction, stagger, fmt::to_string(derivType))].insert(methodName); + registeredMethods[getKey(direction, stagger, fmt::to_string(derivType))].insert( + methodName); }; /// Templated versions of the above registration routines. @@ -385,8 +387,8 @@ struct DerivativeStore { defaultMethods[getKey(theDirection, STAGGER::C2L, theDerivTypeString)] = theDefault; output_verbose << "The default method for staggered derivative type " - << theDerivTypeString << " in direction " << fmt::to_string(theDirection) - << " is " << theDefault << "\n"; + << theDerivTypeString << " in direction " + << fmt::to_string(theDirection) << " is " << theDefault << "\n"; } } } diff --git a/include/bout/field2d.hxx b/include/bout/field2d.hxx index 1ad1f5bc06..02a2ebe620 100644 --- a/include/bout/field2d.hxx +++ b/include/bout/field2d.hxx @@ -46,10 +46,10 @@ class Field2D; #include "bout/fieldperp.hxx" #include "bout/region.hxx" +#include "fmt/format.h" #include #include #include -#include "fmt/format.h" #include #include @@ -617,7 +617,7 @@ inline Field2D& residual(Field2D& f) { return *(f.timeDeriv()); } template <> inline std::string toString<>([[maybe_unused]] const Field2D& val) { return ""; - } +} /// Test if two fields are the same, by calculating /// the minimum absolute difference between them @@ -626,6 +626,7 @@ bool operator==(const Field2D& a, const Field2D& b); std::ostream& operator<<(std::ostream& out, const Field2D& value); // Tell {fmt} not to treat these as ranges so it uses format_as() instead -template <> struct fmt::is_range : std::false_type {}; +template <> +struct fmt::is_range : std::false_type {}; #endif /* BOUT_FIELD2D_H */ diff --git a/include/bout/field3d.hxx b/include/bout/field3d.hxx index 2b7c8e7ec6..c5af15ebdb 100644 --- a/include/bout/field3d.hxx +++ b/include/bout/field3d.hxx @@ -1005,7 +1005,7 @@ inline Field3D& residual(Field3D& f) { return *(f.timeDeriv()); } template <> inline std::string toString<>([[maybe_unused]] const Field3D& val) { return ""; - } +} /// Test if two fields are the same, by calculating /// the minimum absolute difference between them @@ -1240,6 +1240,7 @@ filledFrom(const Field3DParallel& f, } // Tell {fmt} not to treat these as ranges so it uses format_as() instead -template <> struct fmt::is_range : std::false_type {}; +template <> +struct fmt::is_range : std::false_type {}; #endif /* BOUT_FIELD3D_H */ diff --git a/include/bout/fieldperp.hxx b/include/bout/fieldperp.hxx index 4a60c48046..d4364d36d9 100644 --- a/include/bout/fieldperp.hxx +++ b/include/bout/fieldperp.hxx @@ -466,7 +466,7 @@ inline void invalidateGuards(FieldPerp& UNUSED(var)) {} template <> inline std::string toString<>([[maybe_unused]] const FieldPerp& val) { return ""; - } +} /// Test if two fields are the same, by checking that they are defined /// at the same y-index, and if the minimum absolute difference @@ -486,6 +486,7 @@ struct is_expr_fieldperp> && is_expr_constant_v>)> {}; // Tell {fmt} not to treat these as ranges so it uses format_as() instead -template <> struct fmt::is_range : std::false_type {}; +template <> +struct fmt::is_range : std::false_type {}; #endif diff --git a/include/bout/options.hxx b/include/bout/options.hxx index cadf695069..5426471130 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -609,8 +609,7 @@ public: // Check that the default values are the same if (!similar(val, def)) { throw BoutException("Inconsistent default values for '{}': '{}' then '{}'", - full_name, bout::utils::variantToString(value), - def); + full_name, bout::utils::variantToString(value), def); } } return val; @@ -666,8 +665,7 @@ public: // Check that the default values are the same if (!similar(val, def)) { throw BoutException("Inconsistent default values for '{}': '{}' then '{}'", - full_name, bout::utils::variantToString(value), - def); + full_name, bout::utils::variantToString(value), def); } } return val; @@ -913,8 +911,8 @@ private: << bout::utils::variantToString(value) << " (" << bout::utils::variantToString(attributes["source"]) << _(") overwritten with:") << "\n" - << "\t\t" << full_name << " = " << fmt::to_string(val) << " (" << source - << ")\n"; + << "\t\t" << full_name << " = " << fmt::to_string(val) << " (" + << source << ")\n"; } else { throw BoutException( _f("Options: Setting a value from same source ({}) to new value " diff --git a/src/solver/solver.cxx b/src/solver/solver.cxx index f6dcb23c5a..cceb5b2794 100644 --- a/src/solver/solver.cxx +++ b/src/solver/solver.cxx @@ -1274,9 +1274,9 @@ std::vector Solver::getJacobianMetadata2D() co metadata.reserve(f2d.size()); for (int i = 0; i < static_cast(f2d.size()); ++i) { - metadata.push_back(JacobianVariableMetadata{i, f2d[i].name, fmt::to_string(f2d[i].location), - f2d[i].evolve_bndry, f2d[i].constraint, - f2d[i].description}); + metadata.push_back(JacobianVariableMetadata{ + i, f2d[i].name, fmt::to_string(f2d[i].location), f2d[i].evolve_bndry, + f2d[i].constraint, f2d[i].description}); } return metadata; @@ -1287,9 +1287,9 @@ std::vector Solver::getJacobianMetadata3D() co metadata.reserve(f3d.size()); for (int i = 0; i < static_cast(f3d.size()); ++i) { - metadata.push_back(JacobianVariableMetadata{i, f3d[i].name, fmt::to_string(f3d[i].location), - f3d[i].evolve_bndry, f3d[i].constraint, - f3d[i].description}); + metadata.push_back(JacobianVariableMetadata{ + i, f3d[i].name, fmt::to_string(f3d[i].location), f3d[i].evolve_bndry, + f3d[i].constraint, f3d[i].description}); } return metadata; @@ -1389,8 +1389,9 @@ std::string Solver::getJacobianMatrixFilename(const std::string& jacobian_export bout::PetscMatrixExportFormat format) { // The directory the output data is stored in const std::string datadir = Options::root()["datadir"]; - const std::string stem = fmt::format("{}/{}_{}_{:06d}", datadir, jacobian_export_prefix, - fmt::to_string(kind), this->jacobian_export_counter++); + const std::string stem = + fmt::format("{}/{}_{}_{:06d}", datadir, jacobian_export_prefix, + fmt::to_string(kind), this->jacobian_export_counter++); return stem + (format == bout::PetscMatrixExportFormat::binary ? ".dat" : ".txt"); } diff --git a/tests/unit/field/test_fieldperp.cxx b/tests/unit/field/test_fieldperp.cxx index 1a04d6cf6a..24a506d8c5 100644 --- a/tests/unit/field/test_fieldperp.cxx +++ b/tests/unit/field/test_fieldperp.cxx @@ -3,6 +3,7 @@ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include "gtest/gtest.h" +#include "fmt/format.h" #include "test_extras.hxx" #include "bout/array.hxx" #include "bout/boutexception.hxx" @@ -12,7 +13,6 @@ #include "bout/output.hxx" #include "bout/unused.hxx" #include "bout/utils.hxx" -#include "fmt/format.h" #include #include diff --git a/tests/unit/sys/test_utils.cxx b/tests/unit/sys/test_utils.cxx index 19a1cc4f5e..3da796ab1c 100644 --- a/tests/unit/sys/test_utils.cxx +++ b/tests/unit/sys/test_utils.cxx @@ -668,7 +668,7 @@ TEST(FunctionTraitsTest, SecondArg) { #ifndef __cpp_lib_erase_if TEST(StdLibBackPorts, EraseIfMultiset) { std::multiset data{3, 3, 4, 5, 5, 6, 6, 7, 2, 1, 0}; - auto divisible_by_3 = [](auto const& x) { return (x % 3) == 0; }; + auto divisible_by_3 = [](const auto& x) { return (x % 3) == 0; }; bout::utils::erase_if(data, divisible_by_3); @@ -679,7 +679,7 @@ TEST(StdLibBackPorts, EraseIfMultiset) { TEST(StdLibBackPorts, EraseIfSet) { std::set data{3, 4, 5, 6, 7, 2, 1, 0}; - auto divisible_by_3 = [](auto const& x) { return (x % 3) == 0; }; + auto divisible_by_3 = [](const auto& x) { return (x % 3) == 0; }; bout::utils::erase_if(data, divisible_by_3); @@ -697,7 +697,7 @@ TEST(StdLibBackPorts, EraseVector) { TEST(StdLibBackPorts, EraseIfVector) { std::vector data{3, 3, 4, 5, 5, 6, 6, 7, 2, 1, 0}; - auto divisible_by_3 = [](auto const& x) { return (x % 3) == 0; }; + auto divisible_by_3 = [](const auto& x) { return (x % 3) == 0; }; bout::utils::erase_if(data, divisible_by_3); From bc1e7ecd9c602c4a3e1de8b95f39eecfac7bb6d5 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 11:32:19 +0100 Subject: [PATCH 26/37] Add missing header --- src/sys/utils.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sys/utils.cxx b/src/sys/utils.cxx index e15f9248bb..04ab16a2b5 100644 --- a/src/sys/utils.cxx +++ b/src/sys/utils.cxx @@ -35,6 +35,7 @@ #include #include "fmt/chrono.h" +#include "fmt/format.h" /************************************************************************** * String routines From abc0a669f7b25e43d486a24f88bc74b191e5e40c Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 11:32:24 +0100 Subject: [PATCH 27/37] Remove more `toString()` calls --- src/solver/impls/arkode/arkode.cxx | 2 +- tests/unit/sys/test_options_adios2.cxx | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/solver/impls/arkode/arkode.cxx b/src/solver/impls/arkode/arkode.cxx index c5dab45dd9..c2fd745988 100644 --- a/src/solver/impls/arkode/arkode.cxx +++ b/src/solver/impls/arkode/arkode.cxx @@ -232,7 +232,7 @@ int ArkodeSolver::init() { callWithSUNContext(ARKStepCreate, suncontext, nullptr, arkode_rhs, simtime, uvec); break; default: - throw BoutException("Invalid treatment: {}\n", toString(treatment)); + throw BoutException("Invalid treatment: {}\n", treatment); } if (arkode_mem == nullptr) { throw BoutException("ARKStepCreate failed\n"); diff --git a/tests/unit/sys/test_options_adios2.cxx b/tests/unit/sys/test_options_adios2.cxx index 1e299e21ab..686a9a3b22 100644 --- a/tests/unit/sys/test_options_adios2.cxx +++ b/tests/unit/sys/test_options_adios2.cxx @@ -235,8 +235,7 @@ TEST_F(OptionsAdios2Test, Field2DWriteCellCentre) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), - toString(CELL_CENTRE)); + EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), CELL_CENTRE); } TEST_F(OptionsAdios2Test, Field2DWriteCellYLow) { @@ -251,8 +250,7 @@ TEST_F(OptionsAdios2Test, Field2DWriteCellYLow) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), - toString(CELL_YLOW)); + EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), CELL_YLOW); } TEST_F(OptionsAdios2Test, Field3DWriteCellCentre) { @@ -267,8 +265,7 @@ TEST_F(OptionsAdios2Test, Field3DWriteCellCentre) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - toString(CELL_CENTRE)); + EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), CELL_CENTRE); } TEST_F(OptionsAdios2Test, Field3DWriteCellYLow) { @@ -283,8 +280,7 @@ TEST_F(OptionsAdios2Test, Field3DWriteCellYLow) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - toString(CELL_YLOW)); + EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), CELL_YLOW); } TEST_F(OptionsAdios2Test, FieldPerpWriteCellCentre) { @@ -305,8 +301,7 @@ TEST_F(OptionsAdios2Test, FieldPerpWriteCellCentre) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["fperp"].attributes["cell_location"].as(), - toString(CELL_CENTRE)); + EXPECT_EQ(data["fperp"].attributes["cell_location"].as(), CELL_CENTRE); EXPECT_EQ(data["fperp"].attributes["yindex_global"].as(), 2); } From 01ee06c79ef472433700df812980cf7e6a1a7df7 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 12:09:12 +0100 Subject: [PATCH 28/37] Restore `toString()` for booleans --- include/bout/utils.hxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 0f034f4319..6192a7e119 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -554,6 +554,14 @@ struct fmt::range_format_kind, Char> { static constexpr auto value = fmt::range_format::disabled; }; +/// Convert a bool to "true" or "false" +inline std::string toString(const bool& val) { + if (val) { + return "true"; + } + return "false"; +} + /// Convert a value to a string /// by writing to a stringstream template From 8080e6e48cf7e4019bb8a68d02c6fac5d58de265 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 12:37:45 +0100 Subject: [PATCH 29/37] Restore original function order --- include/bout/utils.hxx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/bout/utils.hxx b/include/bout/utils.hxx index 6192a7e119..b80399561a 100644 --- a/include/bout/utils.hxx +++ b/include/bout/utils.hxx @@ -523,6 +523,15 @@ inline void checkData(BoutReal UNUSED(f)) {}; */ char* copy_string(const char* s); +/// Convert a value to a string +/// by writing to a stringstream +template +std::string toString(const T& val) { + std::stringstream ss; + ss << val; + return ss.str(); +} + template inline std::string toString(const Array& UNUSED(val)) { return ""; @@ -562,15 +571,6 @@ inline std::string toString(const bool& val) { return "false"; } -/// Convert a value to a string -/// by writing to a stringstream -template -std::string toString(const T& val) { - std::stringstream ss; - ss << val; - return ss.str(); -} - /*! * Convert a string to lower case */ From 3963b9e175e3bcc1ee2cb0e903a19978980727f1 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 15 Oct 2026 14:31:51 +0100 Subject: [PATCH 30/37] Fix FieldFactoryCreationTest Define `Options options;` above `FieldFactory factory;` so that C++ destroys them in the correct order when the test finishes. --- tests/unit/field/test_field_factory.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/field/test_field_factory.cxx b/tests/unit/field/test_field_factory.cxx index cbffccabc5..58370b5cff 100644 --- a/tests/unit/field/test_field_factory.cxx +++ b/tests/unit/field/test_field_factory.cxx @@ -32,9 +32,10 @@ using bout::generator::Context; // Reuse the "standard" fixture for FakeMesh template class FieldFactoryCreationTest : public FakeMeshFixture { + Options options; + public: FieldFactoryCreationTest() : FakeMeshFixture() { - Options options; options["input"]["transform_from_field_aligned"] = false; factory = FieldFactory{mesh, &options}; } From a568f2e02395a37b77e6a0c0a5b705510ac03160 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Fri, 16 Oct 2026 13:18:47 +0100 Subject: [PATCH 31/37] Replace `toString` with `fmt::to_string` for `operator<<` on field types and Options. --- include/bout/options.hxx | 2 +- src/field/field2d.cxx | 2 +- src/field/field3d.cxx | 2 +- src/field/fieldperp.cxx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bout/options.hxx b/include/bout/options.hxx index 5426471130..bac56ff113 100644 --- a/include/bout/options.hxx +++ b/include/bout/options.hxx @@ -1025,7 +1025,7 @@ void saveParallel(Options& opt, const std::string& name, const Field3D& tosave); /// convertible to an `Options`, and we want _exactly_ `Options` template > inline std::ostream& operator<<(std::ostream& out, const T& value) { - return out << toString(value); + return out << fmt::to_string(value); } namespace bout { diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index dca47b8dc6..19c73f7d35 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -382,7 +382,7 @@ bool operator==(const Field2D& a, const Field2D& b) { } std::ostream& operator<<(std::ostream& out, const Field2D& value) { - out << toString(value); + out << fmt::to_string(value); return out; } diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index 34a8594bc5..ee71206823 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -907,7 +907,7 @@ bool operator==(const Field3D& a, const Field3D& b) { } std::ostream& operator<<(std::ostream& out, const Field3D& value) { - out << toString(value); + out << fmt::to_string(value); return out; } diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index cb0bfe6883..99ba1d9a35 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -245,7 +245,7 @@ bool operator==(const FieldPerp& a, const FieldPerp& b) { } std::ostream& operator<<(std::ostream& out, const FieldPerp& value) { - out << toString(value); + out << fmt::to_string(value); return out; } From 7dc1724215fadb2d39004620bf3b315ec88b1092 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sun, 18 Oct 2026 19:47:00 +0100 Subject: [PATCH 32/37] Call `fmt::to_string` on enums when using in the argument to EXPECT_EQ. --- tests/unit/sys/test_options_adios2.cxx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/unit/sys/test_options_adios2.cxx b/tests/unit/sys/test_options_adios2.cxx index 686a9a3b22..29c404e1ec 100644 --- a/tests/unit/sys/test_options_adios2.cxx +++ b/tests/unit/sys/test_options_adios2.cxx @@ -235,7 +235,8 @@ TEST_F(OptionsAdios2Test, Field2DWriteCellCentre) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), CELL_CENTRE); + EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), + fmt::to_string(CELL_CENTRE)); } TEST_F(OptionsAdios2Test, Field2DWriteCellYLow) { @@ -250,7 +251,8 @@ TEST_F(OptionsAdios2Test, Field2DWriteCellYLow) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), CELL_YLOW); + EXPECT_EQ(data["f2d"].attributes["cell_location"].as(), + fmt::to_string(CELL_YLOW)); } TEST_F(OptionsAdios2Test, Field3DWriteCellCentre) { @@ -265,7 +267,8 @@ TEST_F(OptionsAdios2Test, Field3DWriteCellCentre) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), CELL_CENTRE); + EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), + fmt::to_string(CELL_CENTRE)); } TEST_F(OptionsAdios2Test, Field3DWriteCellYLow) { @@ -280,7 +283,8 @@ TEST_F(OptionsAdios2Test, Field3DWriteCellYLow) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), CELL_YLOW); + EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), + toString(CELL_YLOW)); } TEST_F(OptionsAdios2Test, FieldPerpWriteCellCentre) { @@ -301,7 +305,8 @@ TEST_F(OptionsAdios2Test, FieldPerpWriteCellCentre) { // Read file Options data = OptionsIO::create(file_options)->read(); - EXPECT_EQ(data["fperp"].attributes["cell_location"].as(), CELL_CENTRE); + EXPECT_EQ(data["fperp"].attributes["cell_location"].as(), + fmt::to_string(CELL_CENTRE)); EXPECT_EQ(data["fperp"].attributes["yindex_global"].as(), 2); } From fca0e8cf6c366e03f09a0e8785f8e1c9e8a68203 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sun, 18 Oct 2026 20:14:40 +0100 Subject: [PATCH 33/37] Include output_bout_types.hxx in field2d.cxx, field3d.cxx, and fieldperp.cxx --- src/field/field2d.cxx | 1 + src/field/field3d.cxx | 3 ++- src/field/fieldperp.cxx | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index 19c73f7d35..2f755bd452 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -40,6 +40,7 @@ #include // for mesh #include #include +#include #include #include diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index ee71206823..d898700988 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -28,7 +28,6 @@ #include "bout/array.hxx" #include "bout/bout_types.hxx" #include "bout/build_defines.hxx" -#include "bout/field2d.hxx" #include "bout/index_derivs_interface.hxx" #include "bout/yboundary_regions.hxx" @@ -53,9 +52,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index 99ba1d9a35..6bf39b51d8 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -34,6 +34,7 @@ #include #include #include +#include #include FieldPerp::FieldPerp(Mesh* localmesh, CELL_LOC location_in, int yindex_in, From 6affe3fb2ebe7124740e229b54c69123e901092a Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sun, 18 Oct 2026 20:46:17 +0100 Subject: [PATCH 34/37] Restore use of `toString()` in StringUtilitiesTests --- tests/unit/sys/test_utils.cxx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/unit/sys/test_utils.cxx b/tests/unit/sys/test_utils.cxx index 3da796ab1c..7bcb48e663 100644 --- a/tests/unit/sys/test_utils.cxx +++ b/tests/unit/sys/test_utils.cxx @@ -1,4 +1,3 @@ -#include "fmt/format.h" #include "bout/utils.hxx" #include "gtest/gtest.h" @@ -528,32 +527,32 @@ TEST(StringUtilitiesTest, BoolToString) { std::string true_string = "true"; std::string false_string = "false"; - EXPECT_EQ(true_string, fmt::to_string(true)); - EXPECT_EQ(false_string, fmt::to_string(false)); + EXPECT_EQ(true_string, toString(true)); + EXPECT_EQ(false_string, toString(false)); } TEST(StringUtilitiesTest, ConstCharToString) { - EXPECT_EQ(std::string("hello"), fmt::to_string("hello")); + EXPECT_EQ(std::string("hello"), toString("hello")); } TEST(StringUtilitiesTest, StringToString) { std::string test_string = "dlkjl872kj"; - EXPECT_EQ(test_string, fmt::to_string(test_string)); + EXPECT_EQ(test_string, toString(test_string)); } TEST(StringUtilitiesTest, IntToString) { int number_int = 42; std::string number_string = "42"; - EXPECT_EQ(number_string, fmt::to_string(number_int)); + EXPECT_EQ(number_string, toString(number_int)); } TEST(StringUtilitiesTest, RealToString) { BoutReal number_real = 3.142e8; std::string number_string = "3.142e+08"; - EXPECT_EQ(number_string, fmt::to_string(number_real)); + EXPECT_EQ(number_string, toString(number_real)); } TEST(StringUtilitiesTest, StringSplit) { From ad7344ae02ab56e2c2a15aefdfed2e3602f0b79e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sun, 18 Oct 2026 21:05:13 +0100 Subject: [PATCH 35/37] Restore use of `toString()` in OptionsTest --- tests/unit/sys/test_options.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/sys/test_options.cxx b/tests/unit/sys/test_options.cxx index 6fd72af85c..f4946b9f93 100644 --- a/tests/unit/sys/test_options.cxx +++ b/tests/unit/sys/test_options.cxx @@ -1137,7 +1137,7 @@ value4 = 3.2 value6 = 12 )"; - EXPECT_EQ(fmt::to_string(option), expected); + EXPECT_EQ(toString(option), expected); } TEST_F(OptionsTest, InvalidFormat) { From 0f858003b70f8c07038dd1a7f0c750fc3193f35a Mon Sep 17 00:00:00 2001 From: tomc271 Date: Sun, 18 Oct 2026 21:20:58 +0100 Subject: [PATCH 36/37] Revert replace `toString` with `fmt::to_string` for `operator<<` on field types --- src/field/field2d.cxx | 3 +-- src/field/field3d.cxx | 5 ++--- src/field/fieldperp.cxx | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/field/field2d.cxx b/src/field/field2d.cxx index 2f755bd452..dca47b8dc6 100644 --- a/src/field/field2d.cxx +++ b/src/field/field2d.cxx @@ -40,7 +40,6 @@ #include // for mesh #include #include -#include #include #include @@ -383,7 +382,7 @@ bool operator==(const Field2D& a, const Field2D& b) { } std::ostream& operator<<(std::ostream& out, const Field2D& value) { - out << fmt::to_string(value); + out << toString(value); return out; } diff --git a/src/field/field3d.cxx b/src/field/field3d.cxx index d898700988..34a8594bc5 100644 --- a/src/field/field3d.cxx +++ b/src/field/field3d.cxx @@ -28,6 +28,7 @@ #include "bout/array.hxx" #include "bout/bout_types.hxx" #include "bout/build_defines.hxx" +#include "bout/field2d.hxx" #include "bout/index_derivs_interface.hxx" #include "bout/yboundary_regions.hxx" @@ -52,11 +53,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -908,7 +907,7 @@ bool operator==(const Field3D& a, const Field3D& b) { } std::ostream& operator<<(std::ostream& out, const Field3D& value) { - out << fmt::to_string(value); + out << toString(value); return out; } diff --git a/src/field/fieldperp.cxx b/src/field/fieldperp.cxx index 6bf39b51d8..cb0bfe6883 100644 --- a/src/field/fieldperp.cxx +++ b/src/field/fieldperp.cxx @@ -34,7 +34,6 @@ #include #include #include -#include #include FieldPerp::FieldPerp(Mesh* localmesh, CELL_LOC location_in, int yindex_in, @@ -246,7 +245,7 @@ bool operator==(const FieldPerp& a, const FieldPerp& b) { } std::ostream& operator<<(std::ostream& out, const FieldPerp& value) { - out << fmt::to_string(value); + out << toString(value); return out; } From eba7b8c7fe7d7c774e496c7814a242f893cc3154 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 19 Oct 2026 09:22:52 +0100 Subject: [PATCH 37/37] Replace one more `toString` with `fmt::to_string` --- tests/unit/sys/test_options_adios2.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/sys/test_options_adios2.cxx b/tests/unit/sys/test_options_adios2.cxx index 29c404e1ec..5b336766d5 100644 --- a/tests/unit/sys/test_options_adios2.cxx +++ b/tests/unit/sys/test_options_adios2.cxx @@ -284,7 +284,7 @@ TEST_F(OptionsAdios2Test, Field3DWriteCellYLow) { Options data = OptionsIO::create(file_options)->read(); EXPECT_EQ(data["f3d"].attributes["cell_location"].as(), - toString(CELL_YLOW)); + fmt::to_string(CELL_YLOW)); } TEST_F(OptionsAdios2Test, FieldPerpWriteCellCentre) {