34

I don't have much experience working with C++. Rather I have worked more in C# and so, I wanted to ask my question by relating to what I would have done in there. I have to generate a specific format of the string, which I have to pass to another function. In C#, I would have easily generated the string through the below simple code.

string a = "test";
string b = "text.txt";
string c = "text1.txt";

String.Format("{0} {1} > {2}", a, b, c);

By generating such an above string, I should be able to pass this in system(). However, system only accepts char*

I am on Win32 C++ (not C++/CLI), and cannot use boost since it would include too much inclusion of all the files for a project which itself is very small. Something like sprintf() looks useful to me, but sprintf does not accept string as the a, b and c parameters. Any suggestions how I can generate these formatted strings to pass to system in my program?

sk8forether
  • 247
  • 2
  • 9
user1240679
  • 6,829
  • 17
  • 60
  • 89
  • 2
    you know that boost won't add any dependencies to your binaries, right? (It will, of course add dependencies to the source) – Shep May 02 '12 at 09:26

7 Answers7

45

The C++ way would be to use a std::stringstream object as:

std::stringstream fmt;
fmt << a << " " << b << " > " << c;

The C way would be to use sprintf.

The C way is difficult to get right since:

  • It is type unsafe
  • Requires buffer management

Of course, you may want to fall back on the C way if performance is an issue (imagine you are creating fixed-size million little stringstream objects and then throwing them away).

dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • `system(fmt)` gives me an erros saying no suitable conversion between `stringstream` to `char*` – user1240679 May 02 '12 at 08:26
  • 2
    system(fmt.str().c_str()) should probably do it. Note that on windows at least, if you call system() and you get errors even though the command and its arguments are correct and properly-quoted, surround the whole thing in quotes and it'll work. – Shadow2531 May 02 '12 at 08:38
  • Two things to note: 1) `system` is implementation defined -- so do not use it if you want to keep your code portable. 2) As others have mentioned, use `fmt.str().c_str()` to get to the `char *` (C-style string) representation of the string. – dirkgently May 02 '12 at 09:06
32

For the sake of completeness, you may use std::stringstream:

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

int main() {
    std::string a = "a", b = "b", c = "c";
    // apply formatting
    std::stringstream s;
    s << a << " " << b << " > " << c;
    // assign to std::string
    std::string str = s.str();
    std::cout << str << "\n";
}

Or (in this case) std::string's very own string concatenation capabilities:

#include <iostream>
#include <string>

int main() {
    std::string a = "a", b = "b", c = "c";
    std::string str = a + " " + b + " > " + c;
    std::cout << str << "\n";
}

For reference:


If you really want to go the C way, here you are:

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>

int main() {
    std::string a = "a", b = "b", c = "c";
    const char fmt[] = "%s %s > %s";
    // use std::vector for memory management (to avoid memory leaks)
    std::vector<char>::size_type size = 256;
    std::vector<char> buf;
    do {
        // use snprintf instead of sprintf (to avoid buffer overflows)
        // snprintf returns the required size (without terminating null)
        // if buffer is too small initially: loop should run at most twice
        buf.resize(size+1);
        size = std::snprintf(
                &buf[0], buf.size(),
                fmt, a.c_str(), b.c_str(), c.c_str());
    } while (size+1 > buf.size());
    // assign to std::string
    std::string str = &buf[0];
    std::cout << str << "\n";
}

For reference:


Since C++11, you can "simplify" this to:

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>

int main() {
    std::string a = "a", b = "b", c = "c";
    const char fmt[] = "%s %s > %s";
    // can use std::string as buffer directly (since C++11)
    std::string::size_type size = 256;
    std::string str;
    do {
        str.resize(size+1);
        // use snprintf instead of sprintf (to avoid buffer overflows)
        // snprintf returns the required size (without terminating null)
        // if buffer is too small initially: loop should run at most twice
        size = std::snprintf(
                &str[0], str.size(),
                fmt, a.c_str(), b.c_str(), c.c_str());
    } while (size+1 > str.size());
    // can strip off null-terminator, as std::string adds their own
    str.resize(size);
    // done
    std::cout << str << "\n";
}

For reference:


Then, there's the Boost Format Library. For the sake of your example:

#include <iostream>
#include <string>
#include <boost/format.hpp>

int main() {
    std::string a = "a", b = "b", c = "c";
    // apply format
    boost::format fmt = boost::format("%s %s > %s") % a % b % c; 
    // assign to std::string
    std::string str = fmt.str();
    std::cout << str << "\n";
}
moooeeeep
  • 31,622
  • 22
  • 98
  • 187
19

In addition to options suggested by others I can recommend the fmt library which implements string formatting similar to str.format in Python and String.Format in C#. Here's an example:

std::string a = "test";
std::string b = "text.txt";
std::string c = "text1.txt";
std::string result = fmt::format("{0} {1} > {2}", a, b, c);

Disclaimer: I'm the author of this library.

vitaut
  • 49,672
  • 25
  • 199
  • 336
10

You can use sprintf in combination with std::string.c_str().

c_str() returns a const char* and works with sprintf:

string a = "test";
string b = "text.txt";
string c = "text1.txt";
char* x = new char[a.length() + b.length() + c.length() + 32];

sprintf(x, "%s %s > %s", a.c_str(), b.c_str(), c.c_str() );

string str = x;
delete[] x;

or you can use a pre-allocated char array if you know the size:

string a = "test";
string b = "text.txt";
string c = "text1.txt";
char x[256];

sprintf(x, "%s %s > %s", a.c_str(), b.c_str(), c.c_str() );
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Shouldn't {0}, {1}, {2} be %s ? And should I delete this char* x after the operation or it doesn't matter? – user1240679 May 02 '12 at 08:32
  • What are {0}, {1}, {2} in sprintf? What is delete[x]? – Igor R. May 02 '12 at 08:32
  • @user1240679 that was a typo, se corrected version. You should delete the char* if allocated with `new`, otherwise it's a memory leak. – Luchian Grigore May 02 '12 at 08:34
  • 2
    @LuchianGrigore: I would seriously advise a combination of `std::vector` and `snprintf` to avoid both leaks and buffer overflows. See [snprintf](http://linux.die.net/man/3/snprintf). – Matthieu M. May 02 '12 at 09:00
3

For completeness, the boost way would be to use boost::format

cout << boost::format("%s %s > %s") % a % b % c;

Take your pick. The boost solution has the advantage of type safety with the sprintf format (for those who find the << syntax a bit clunky).

Shep
  • 7,990
  • 8
  • 49
  • 71
2

As already mentioned the C++ way is using stringstreams.

#include <sstream>

string a = "test";
string b = "text.txt";
string c = "text1.txt";

std::stringstream ostr;
ostr << a << " " << b << " > " << c;

Note that you can get the C string from the string stream object like so.

std::string formatted_string = ostr.str();
const char* c_str = formatted_string.c_str();
2

You can just concatenate the strings and build a command line.

std::string command = a + ' ' + b + " > " + c;
system(command.c_str());

You don't need any extra libraries for this.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 1
    As far as I am aware C++ does not optimise this, so this would copy the the string for each operation, first creating `a + ' '` then, `a + ' ' + b`, etc. – c z Jan 27 '21 at 13:07