Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
like image 717
jilt3d Avatar asked Feb 22 '11 09:02

jilt3d


3 Answers

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.

like image 162
SirDarius Avatar answered Sep 28 '22 10:09

SirDarius


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.

like image 24
visitor Avatar answered Sep 28 '22 12:09

visitor


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?

like image 34
Martin Stone Avatar answered Sep 28 '22 12:09

Martin Stone