How to pass not variadic values to fmt::format?
Asked Answered
A

4

5

I'm playing with the great fmt C++ library to format strings more gracefully.

And I'd like to pass a non-variable arguments list to fmt::format. It could be a std::vector, or std::string, or whatever, but it will always match the format string.

So fmt::format works like:

std::string message = fmt::format("The answer is {} so don't {}", "42", "PANIC!");

But what I'd like is something like:

std::vector<std::string> arr;
arr.push_back("42");
arr.push_back("PANIC!");
std::string message = fmt::format("The answer is {} so don't {}", arr);

Is there a way / workaround to do so?

Armin answered 19/2, 2018 at 23:2 Comment(0)
D
4

Add an extra layer, something like:

template <std::size_t ... Is>
std::string my_format(const std::string& format,
                      const std::vector<std::string>& v,
                      std::index_sequence<Is...>)
{
    return fmt::format(format, v[Is]...);
}


template <std::size_t N>
std::string my_format(const std::string& format,
                      const std::vector<std::string>& v)
{
    return my_format(format, v, std::make_index_sequence<N>());
}

Usage would be:

std::vector<std::string> arr = {"42", "PANIC!"};
my_format<2>("The answer is {} so don't {}", arr);

With operator ""_format you might have the information about expected size at compile time

Dorena answered 20/2, 2018 at 3:32 Comment(0)
G
9

You can build up the arguments to vformat on your own from your vector. This seems to work:

std::string format_vector(std::string_view format,
    std::vector<std::string> const& args)
{
    using ctx = fmt::format_context;
    std::vector<fmt::basic_format_arg<ctx>> fmt_args;
    for (auto const& a : args) {
        fmt_args.push_back(
            fmt::internal::make_arg<ctx>(a));
    }

    return fmt::vformat(format,
        fmt::basic_format_args<ctx>(
            fmt_args.data(), fmt_args.size()));
}

std::vector<std::string> args = {"42", "PANIC!"};
std::string message = format_vector("The answer is {} so don't {}", args);
Georgetta answered 15/1, 2020 at 3:26 Comment(0)
D
4

Add an extra layer, something like:

template <std::size_t ... Is>
std::string my_format(const std::string& format,
                      const std::vector<std::string>& v,
                      std::index_sequence<Is...>)
{
    return fmt::format(format, v[Is]...);
}


template <std::size_t N>
std::string my_format(const std::string& format,
                      const std::vector<std::string>& v)
{
    return my_format(format, v, std::make_index_sequence<N>());
}

Usage would be:

std::vector<std::string> arr = {"42", "PANIC!"};
my_format<2>("The answer is {} so don't {}", arr);

With operator ""_format you might have the information about expected size at compile time

Dorena answered 20/2, 2018 at 3:32 Comment(0)
S
3

In the current version (8.1.1) the following is possible;

fmt::dynamic_format_arg_store<fmt::format_context> ds;

ds.push_back(42);
ds.push_back("PANIC");

std::string msg = fmt::vformat("The answer is {} so don't {}", ds);
Schmaltz answered 8/4, 2022 at 8:55 Comment(0)
D
1

It doesn't look like this is possible without making changes to the fmt library. fmt::format calls fmt::vformat which takes a fmt::format_args or fmt::wformat_args object representing multiple arguments, but the only ways provided to create format_args or wformat_args objects are via another variadic function, which means the number and types of the arguments must be known at compile time.

So you could write a wrapper to unpack a std::tuple or std::array and pass its elements to fmt::format, because the number and types of elements in those is known at compile time. But you can't do the same with a std::vector, std::list, etc., since the size of those containers can vary at runtime.

Dorado answered 20/2, 2018 at 0:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.