jilt3d
jilt3d

Reputation: 4034

How can I format a std::string using a collection of arguments?

Is it possible to format std::string passing a set of arguments?

Currently I am formatting the string this way:

string helloString = "Hello %s and %s";
vector<string> tokens; //initialized vector of strings
const char* helloStringArr = helloString.c_str();
char output[1000];
sprintf_s(output, 1000, helloStringArr, tokens.at(0).c_str(), tokens.at(1).c_str());

But the size of the vector is determined at runtime. Is there any similar function to sprintf_s which takes a collection of arguments and formats a std::string/char*? My development environment is MS Visual C++ 2010 Express.

EDIT: I would like to achieve something similar:

sprintf_s(output, 1000, helloStringArr, tokens);

Upvotes: 18

Views: 42026

Answers (3)

visitor
visitor

Reputation: 8834

You can do it with the Boost.Format library, because you can feed the arguments one by one.

This actually enables you to achieve your goal, quite unlike the printf family where you have to pass all the arguments at once (i.e you'll need to manually access each item in the container).

Example:

#include <boost/format.hpp>
#include <string>
#include <vector>
#include <iostream>
std::string format_range(const std::string& format_string, const std::vector<std::string>& args)
{
    boost::format f(format_string);
    for (std::vector<std::string>::const_iterator it = args.begin(); it != args.end(); ++it) {
        f % *it;
    }
    return f.str();
}

int main()
{
    std::string helloString = "Hello %s and %s";
    std::vector<std::string> args;
    args.push_back("Alice");
    args.push_back("Bob");
    std::cout << format_range(helloString, args) << '\n';
}

You can work from here, make it templated etc.

Note that it throws exceptions (consult documentation) if the vector doesn't contain the exact amount of arguments. You'll need to decide how to handle those.

Upvotes: 10

SirDarius
SirDarius

Reputation: 42869

The most C++-ish way to achieve sprintf-like functionality would be to use stringstreams.

Here is an example based on your code:

#include <sstream>

// ...

std::stringstream ss;
std::vector<std::string> tokens;
ss << "Hello " << tokens.at(0) << " and " << tokens.at(1);

std::cout << ss.str() << std::endl;

Pretty handy, isn't it ?

Of course, you get the full benefit of IOStream manipulation to replace the various sprintf flags, see here http://www.fredosaurus.com/notes-cpp/io/omanipulators.html for reference.

A more complete example:

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

int main() {
  std::stringstream s;
  s << "coucou " << std::setw(12) << 21 << " test";

  std::cout << s.str() << std::endl;
  return 0;
}

which prints:

coucou           21 test

Edit:

As pointed by the OP, this way of doing things does not allow for variadic arguments, because there is no `template' string built beforehand allowing the stream to iterate over the vector and insert data according to placeholders.

Upvotes: 14

Martin Stone
Martin Stone

Reputation: 13007

The boost::format library might be of interest if you want to avoid having to manually deal with the output buffer.

As for taking the plain vector as input, what would you want to happen if tokens.size()<2? Wouldn't you have to ensure that the vector was big enough to index elements 0 and 1 in any case?

Upvotes: 1

Related Questions