Fmt: Add an API for dynamic construction of argument lists

Created on 21 May 2019  路  13Comments  路  Source: fmtlib/fmt

Currently the number and types (but not values) of formatting arguments has to be known at compile time. Consider adding an API to allow fully dynamic construction of argument lists to support use cases like the one mentioned in #1168:

I have a few use cases, but they all boil down to wanting to externalize the format of whatever needs to be produced from the C++ code. So, for example, a config file might have a format string in it and that format string wants to extract zero or more named values from some data dictionary in the program.

enhancement

Most helpful comment

I think the easiest solution is to implement something like dynamic_arg_store which is similar to format_arg_store but owns the arguments and allows adding them dynamically. It can provide a conversion operator to basic_format_args that will allow passing it to vformat and other formatting functions once it's populated.

All 13 comments

Adding on to #1168, I was working on a resume generator and ran into a similar issue.

One of the features I was working on was the ability for the user to define HTML templates like below:

        <header>
          <h1 id="name">Vincent La</h1>
          <p>
            <span>{Address}</span>
            <span>{Email}</span>
            <span>{LinkedIn}</span>
            <span>{Website}</span>
          </p>
        </header>

The main problem is that I want to be able to define the templates without having to touch C++ code. That means the parameters have to been parsed from some sort of data file, so the formatting arguments have to be constructed at runtime. In Python, I would just build a dict of arguments and pass them into str.format() directly. I was disappointed I couldn't do that here, and ended up writing my own function to do the formatting.

With that being said, I really liked a lot of the other features this library provided. I'm willing to help implement this if you have good pointers on where to start.

I think the easiest solution is to implement something like dynamic_arg_store which is similar to format_arg_store but owns the arguments and allows adding them dynamically. It can provide a conversion operator to basic_format_args that will allow passing it to vformat and other formatting functions once it's populated.

Qt has a format API which looks like QString("%1, %2!").arg("hello").arg("world"), this operation will look and replace the placeholder %1 and %2 inside str.
Seems not so difficult to realize?
But in this way performance may not be as good.

Adding dynamic_arg_store as described would be extremely useful. My typical use case is that I have a std::map and I want to add one or more of these to the dynamic_arg_store and then pass this to vformat. Ideally I could keep the dynamic_arg_store around and update it in place, and also used the new compiled format object with it to speed up the operation.

While dynamic_arg_store is not offered i'm using currying based on kari.hpp :

#include <fmt/format.h>
#include <https://raw.githubusercontent.com/BlackMATov/kari.hpp/master/headers/kari.hpp/kari.hpp>
namespace fmt {

    namespace detail {

        template<class C>
        struct FormatWrapper {
            C c;

            FormatWrapper(const C& c)
            : c(c) {
            }

            std::string operator()() const {
                return c();
            }

            template <typename... As >
            auto operator()(As&&... as) const {
                auto c2 = c(std::forward<As>(as)...);
                return FormatWrapper < std::decay_t<decltype(c2)>>(std::move(c2));
            }

            operator std::string() const {
                return c();
            }
        };
    }

    inline auto Format(fmt::string_view format_str) {
        auto c = kari::curryV([format_str](auto&&... args) {
            return fmt::format(format_str, std::forward<decltype(args)>(args)...);
        });
        return fmt::detail::FormatWrapper<decltype(c)>(c);
    }
}

void main() {
    auto f = fmt::Format("{} {} {}");
    auto f1 = f("foo");
    auto f2 = f1("bar")("baz");
    std::string s = f2;
    fmt::print("{}\n", s);
}

While dynamic_arg_store is not offered i'm using currying based on [kari.hpp]

Does this support runtime determine number of arguments? i.e. I have my zero or more named args in a runtime constructed map and I want to pass them all to the Format function.

Does this support runtime determine number of arguments? i.e. I have my zero or more named args in a runtime constructed map and I want to pass them all to the Format function.

True. Definetely no. It's only compile time wrapper for compile time variadic args function fmt::format()

Is this feature still on the radar?

It is but AFAIK noone is working on it at the moment.

Hm, I'm wondering if there's something wrong with just making internal::make_arg<>() public (I mean considering it as a part of interface and not as implementation detail) or providing a public wrapper around it?

There's even a unit test with dynamic arguments: TEST(FormatTest, Dynamic)

UPD: I realize lifetime for strings and custom objects is an issue but in may cases it's acceptable. E.g. in my case there's a binary dump and a format string to format it, char sequence is already stored in the dump, so its lifetime is guaranteed.

Lifetime is an issue. Even though it may be acceptable in some cases, making it public would create a big footgun.

Thank you for your answer. I will give a try to add dynamic_arg_store.

As a side note I would say, it's debatable to some extent. E.g. STL doesn't prevent one from creating a container with pointers and either the programmer takes care of lifetime or UB happens.

This has been implemented in #1584. Thanks to @vsolontsov-ll!

Was this page helpful?
0 / 5 - 0 ratings