Ich fand eine Lösung mit fmtlib Interna. Der folgende Code formatiert mithilfe von fmtlib einen String aus einem String-String-Dictionary. Für arg-counts> = 16 musste eine spezielle Behandlung eingeschlossen werden, da fmtlib eine Optimierung für kleinere Argumentlisten verwendet.
// helper only:
inline void set_type(fmt::ULongLong& result, uint32_t index, fmt::internal::Arg::Type t)
{
unsigned shift = index * 4;
uint64_t mask = 0xf;
result |= static_cast<uint64_t>(t) << shift;
}
// input:
// pattern = fmt::format string
// vars = dictionary of string/string arguments
// output:
// formatted string
std::string dformat(const std::string& pattern, const std::unordered_map<std::string, std::string>& vars)
{
// this is a vector of "named arguments" - straightforward enough.
std::vector<fmt::internal::NamedArg<char>> av;
// fmtlib uses an optimization that stores the types of the first 16 arguments as
// bitmask-encoded 64-bit integer.
fmt::ULongLong types = 0;
// we need to build the named-arguments vector.
// we cannot resize it to the required size (even though we know it - we have the
// dictionary), because NamedArg has no default constructor.
uint32_t index = 0;
for (const auto& item : vars)
{
av.emplace_back(fmt::internal::NamedArg<char>(item.first, item.second));
// we need to pack the first 16 arguments - see above
if (index < fmt::ArgList::MAX_PACKED_ARGS)
{
set_type(types, index, fmt::internal::Arg::NAMED_ARG);
}
++index;
}
// and this is a bit tricky: depending on the number of arguments we use two mutually
// incompatible vectors to create an arglist. It has everything to do with the speed
// (and memory) optimization above, even though the code looks nearly identical.
if (index >= fmt::ArgList::MAX_PACKED_ARGS)
{
std::vector<fmt::internal::Arg> avdata;
// note the additional terminating Arg::NONE
avdata.resize(vars.size() + 1);
index = 0;
for (const auto& item : av)
{
avdata[index].type = fmt::internal::Arg::NAMED_ARG;
avdata[index].pointer = &av[index];
++index;
}
return fmt::format(pattern, fmt::ArgList(types, &avdata[0]));
}
else
{
std::vector<fmt::internal::Value> avdata;
// no need for terminating Arg::NONE, because ARG_NONE is the last encoded type
avdata.resize(vars.size());
index = 0;
for (const auto& item : av)
{
avdata[index].pointer = &av[index];
++index;
}
return fmt::format(pattern, fmt::ArgList(types, &avdata[0]));
}
}
Beispiel Nutzung:
std::unordered_map<std::string, std::string> vars;
vars["FIRSTNAME"] = "Foo";
vars["LASTNAME"] = "Bar";
std::string result = dformat("Hello {FIRSTNAME} {LASTNAME}, how are you doing", vars);
Ohne zu tief in den Code einzutauchen dosn't es möglich zu sein scheinen. Benutze entweder String-Streams oder schreibe die [fmt libraries write API] (http://fmtlib.net/3.0.0/#write-api)? Oder wenn die Daten, die Sie drucken möchten, die gleichen sind, dann verwenden Sie vielleicht etwas, wie in [dieses Problem bezüglich 'std :: initializer_list'] (https://github.com/fmtlib/fmt/issues/181) beschrieben, sollte es leicht zu ersetzen sein mit z ein 'std :: vector'. Unter Verwendung der in der Ausgabe dargestellten Lösung könnten Sie z.B. ein Vektor von 'std :: any' (oder' boost :: any'). Wenn Sie keinen einheitlichen Typ für alle Artikel haben, die Sie drucken möchten. –