4

What is the "proper way" to do the following? (Note, I don't want to output the message to the screen (yet), the data needs to be stored in a variable.)

std::cout << "Enter a letter: ";    
char input;
std::cin >> input;

std::string message = "Today's program was brought to you by the letter '" + input + "'.";

The code gives me the error message invalid operands of types const char* and const char [3] to binary operator+.

I understand why this message is occurring. When Googling for a solution, the results that come up recommend casting each item into a string in turn. However, this becomes impractical if you have to concatenate a dozen items:

std::string("x:") + x + std::string(", y:") + y + std::string(", width:") + width + std::string(", height:") + height + ...;

What is the "proper way" in c++ to concatenate strings, chars, char arrays, and any other data that is convertible, into a single string? Is there anything like Python's beautiful string formatting features in c++?

IQAndreas
  • 8,060
  • 8
  • 39
  • 74
  • Yes, Boost.Format is somewhat like Python and there are plenty of small, modern, typesafe formatting libraries. – chris Aug 28 '15 at 04:31

2 Answers2

4

What you are trying to do won't work because C++ views your concatenation as an attempt to add several char pointers. If you explicitly cast the first element in the series to an std::string it should work.

Change your original code

string message = "Today's program was brought to you by the letter '" + input + "'.";

to this:

string message = std::string("Today's program was brought to you by the letter '")
    + input + "'.";

q.v. this SO post which discusses this problem in greater detail (though I don't know why it got closed as not being a real question).

Community
  • 1
  • 1
Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
  • But as I mentioned in the question, if I'm adding together a collection of a dozen items (e.g. `"a:" + a + ", b:" + b + ", c:" + c + ...`) isn't that going to result in a lot of `std::string` casts? – IQAndreas Aug 28 '15 at 04:35
  • 5
    You only need to construct a `std::string` from the first element. `std::string` + string literal is a defined operation – David Stone Aug 28 '15 at 04:39
  • @DavidStone Ah, yes, I see! The `+` operation on the first element returns another `std::string` which has that operation defined on it, etc. – IQAndreas Aug 28 '15 at 04:49
  • 1
    In fact, in that case the code can be _even_ simpler by starting the line with a "dummy" empty string: `std::string() + "Today's program..." + input` – IQAndreas Aug 28 '15 at 04:50
  • @IQAndreas Very nice answer. – Tim Biegeleisen Aug 28 '15 at 04:51
2

There's several ways to do this, but one way that's a good balance between simplicity of implementation and convenience is to use a "formatter" class which wraps std::stringstream like so:

string message = formatter() << "Today's program was brought to you by the letter '" << input << "'.";

Where formatter can be defined very simply in a header file as follows:

#include <sstream>

class formatter {
public:
    template <typename T>
    formatter & operator<<(const T & o) {
        stream_ << o;
        return *this;
    }

    const std::string str() const { return stream_.str(); }

    operator std::string() {
            return stream_.str();
    }

private:
    std::ostringstream stream_;
};

What's going on there: If you try to use a temporary std::stringstream() instead of formatter() above, it doesn't work because

  • std::stringstream is not implicitly convertible to std::string
  • You can't even do it like this std::string message = (std::stringstream() << "foo" << input << "bar").str(); because, std::stringstream returns std::ostream & from its stream operations (rather than std::stringstream &), and you cannot convert an ostream to a string in general.

The formatter class just lets you construct and use a stringstream all in one line with a minimum of boiler plate.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • 1
    An important benefit here is that this approach lets you stream numbers and other types that you can't directly concatenate to a `string`. `operator std::string()` can be made `const`. For whatever it's worth - `static_cast(std::ostringstream() << x << y << z).str()` "works" but is too clumsy and verbose to be desirable for widespread use.... – Tony Delroy Aug 28 '15 at 05:00