Behnam
Behnam

Reputation: 31

format string with a variable size vector of arguments (e.g. pass vector of arguments to std::snprintf)

I'm looking for a way to format a string with a variable-size vector of variables. What do you suggest is the best way of doing this?

I already know about std::snprintf and std::vsnprintf but unfortunately none works out of the box for my problem. Also a solution with recursive templates wont work for me because I can't rely on the input format being fully defined at compile time.

Here is a sample interface for the function I'm trying to implement.

std::string format_variable_size(const char* format, const std::vector<int>& in) {
std::string out{};
....
return out;
}

Example input and output:

const char* format = "My first int is %d, my second int is: %d, my float is: %d";
std::vector<int> in = {1,2,3};

the format_variable_size would return

out = "My first int is 1, my second int is: 2, my float is: 3"

Another example:

const char* format = "My first int is %d, my second int is: %d";
std::vector<int> in = {1,2};

the format_variable_size would return

"My first int is 1, my second int is: 2"

Thanks,

Upvotes: 0

Views: 1528

Answers (3)

康桓瑋
康桓瑋

Reputation: 42861

Not pretty solution, since we can't get std::vector's size at compile time:

template <std::size_t... I>
std::string
format_variable_size_impl(const char* format, const std::vector<int>& in, 
                          std::index_sequence<I...>)
{
    // Determine the necessary buffer size
    auto size = std::snprintf(nullptr, 0, format, in[I]...);
    std::string out(size + 1, 0);
    std::sprintf(out.data(), format, in[I]...);
    return out;
}

std::string 
format_variable_size(const char* format, const std::vector<int>& in) 
{
    if (in.size() == 0)
        return format;
    if (in.size() == 1)
        return format_variable_size_impl(format, in, std::make_index_sequence<1>{});
    if (in.size() == 2)
        return format_variable_size_impl(format, in, std::make_index_sequence<2>{});
    if (in.size() == 3)
        return format_variable_size_impl(format, in, std::make_index_sequence<3>{});
    if (in.size() == 4)
        return format_variable_size_impl(format, in, std::make_index_sequence<4>{});
    if (in.size() == 5)
        return format_variable_size_impl(format, in, std::make_index_sequence<5>{});
    // ...
}

Upvotes: 0

Phil1970
Phil1970

Reputation: 2624

If the only specifier you use is %d, then you can easily do a loop and manually replace with the value coming from the vector. Alternatively, you might consider defining your own replacement token (for example ###) to simplify parsing.

Also, if you can live with relatively small vector size (say maximum numbers), you could simply do something like this:

std::vector<int> copy(in);
copy.resize(10);
std::snprintf(buffer, buffer_size, 
    copy[0], copy[1], copy[2], copy[3], copy[4],
    copy[5], copy[6], copy[7], copy[8], copy[9]);
  • If you format string contains less %d than the size of passed vector, then only the first ones will be outputted.
  • If the size match exactly, you get the expected result.
  • If the number of %d is greater than the input vector and up to the size of the copy, extra %d will be outputted with 0.
  • If you have too much %d, then the behavior is undefined.
    • If the format string is internal, this might be acceptable but if it come from user or a file, it is better to validate the string (in which case, manually processing might be attractive)

Upvotes: 0

Vivick
Vivick

Reputation: 4991

If you have nothing against using fmt, I think the following might work :

#include <numeric>

std::string format_variable_size(const char* fmt, std::vector<int> args){
  return std::accumulate(
    std::begin(args),
    std::end(args),
    std::string{fmt},
    [](std::string toFmt, int arg){
      return fmt::format(toFmt, arg);
    }
  );
}

std::vector<int> v = {1,2,3};
std::cout << format_variable_size("[{}, {}, {}]\n", v);

Upvotes: 2

Related Questions