GersonKurz
GersonKurz

Reputation: 71

building a dynamic list of named arguments for {fmt}

I am using the {fmt} library and need to build a dynamic list of arguments. Basically, I have a format string like per the documentation page

fmt::print("Hello, {name}", fmt::arg("name", "test"), ...);

but the arguments list (including how many arguments there are) is known only at runtime. I've looked at fmt::ArgList which takes a list of fmt::Arg instances. But the named arguments are an internal class fmt::internal::NamedArg which I cannot see how to pass to the list.

Any ideas?

Upvotes: 4

Views: 3629

Answers (2)

Siddu
Siddu

Reputation: 136

{fmt} has inbuilt support for dynamic argument list starting with 7.0.0.

Named dynamic args:

fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("const", "pi"));
store.push_back(fmt::arg("val", 3.14f));

std::string result = fmt::vformat("{const} = {val}", store);
// result is "pi = 3.14"

Unnamed dynamic args:

fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back("answer to everything");
store.push_back(42);

std::string result = fmt::vformat("{} is {}", store);
// result is "answer to everything is 42"

Upvotes: 8

GersonKurz
GersonKurz

Reputation: 71

I found a solution using fmtlib internals. The following code formats a string from a string-string dictionary using fmtlib. Special handling had to be included to handle arg-counts >= 16, as fmtlib uses an optimization for smaller argument lists.

// 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]));
    }
}

Example usage:

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);

Upvotes: 3

Related Questions