17

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);
jilt3d
  • 3,864
  • 8
  • 34
  • 41
  • possible duplicate of [std::string formating like sprintf](http://stackoverflow.com/questions/2342162/stdstring-formating-like-sprintf) – Rup Feb 22 '11 at 10:07
  • Is the format determined at runtime too ? Or is it fixed ? Do you simply want to enumerate the content of the `vector` in your output ? You might want to check out "join": http://www.boost.org/doc/libs/1_46_0/doc/html/string_algo/reference.html#header.boost.algorithm.string.join_hpp – Matthieu M. Feb 22 '11 at 10:30
  • The format is also determined at runtime. See the EDIT section for more details. Thanks! – jilt3d Feb 22 '11 at 12:10

3 Answers3

13

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.

SirDarius
  • 41,440
  • 8
  • 86
  • 100
  • Thanks, but I still can not see how can I pass the vector as an argument without getting values one by one. I need something similar: sprintf_s(output, 1000, helloStringArr, tokens); – jilt3d Feb 22 '11 at 10:15
  • The C++-ish way would still be to add the tokens to a stringstream, instead of first building a collection of tokens and then try to reformat them again. It would probably help if we knew what we are doing, rather than how we are supposed to do it. :-) – Bo Persson Feb 22 '11 at 18:25
  • @Bo: So the C++ish way is to not use format strings, and just give up all the goodness, such as readability ;) or basic internationalization? (The string fragments you have to feed to streams are basically not meaningfully translatable) – visitor Feb 23 '11 at 10:15
  • @visitor: Readability perhaps isn't the real strength of format strings. :-). Internationalization will also be pretty basic, if you don't consider that the order of the items might be affected by the language, or that the phrases used depends on the number of items. – Bo Persson Feb 23 '11 at 16:17
9

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.

visitor
  • 8,564
  • 2
  • 26
  • 15
1

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?

Martin Stone
  • 12,682
  • 2
  • 39
  • 53
  • I've checked this library, but doesn't find a way to pass formatting arguments as one set of arguments. – jilt3d Feb 22 '11 at 10:19