10
\$\begingroup\$

I always thought it would be handy to be able to write:

const std::string s = (std::ostringstream() << "hi" << 0).str();

This doesn't work in C++ by default because operator<< returns the ostringstream as a ostream, which obviously doesn't have the str() member.

I was pleasantly surprised though to discover that this can be done in C++11:

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

template <typename CharT, typename traits, typename T>
std::basic_ostringstream<CharT,traits>&& operator<<(std::basic_ostringstream<CharT,traits>&& out, const T& t) {
  static_cast<std::basic_ostream<CharT,traits>&>(out) << t;
  return std::move(out);
}

int main() {
  const std::string s = (std::ostringstream() << "hi" << 0).str();
  std::cout << s << std::endl;
}

It needs rvalue references and move semantics to bind the temporary to a reference here.

  1. Is there a good reason why this isn't provided by the standard library by default? Is it an oversight? Making this idiom legal code seems like it might offer a number of benefits.
  2. In my local C++11 project, I provide some "utility" code. Is there any reason why I shouldn't include this and use it widely?

How about the more generic version:

template <typename S, typename T, class = typename
               std::enable_if<std::is_base_of<std::basic_ostream<typename S::char_type,typename S::traits_type>, S>::value>::type>
S&& operator<<(S&& out, const T& t) {
  static_cast<std::basic_ostream<typename S::char_type,typename S::traits_type>&>(out) << t;
  return std::move(out);
}
\$\endgroup\$
1

4 Answers 4

6
\$\begingroup\$

Bjarne gives a pretty succinct description of why this (and many similar) features aren't in the standard. He points out that virtually everybody who talks to him about C++ has essentially the same thing to say: "C++ is way to big and way too complex. You should really work at making it a lot smaller and simpler. Oh, but while you're at it, you should add this one really great feature I thought of"

It kind of reminds me of things I've seen about how questions of political polls can be/ are phrased. If I ask: "Do you think this country should do more to [increase jobs | help the needy | etc.]?" I can count on a large majority of people saying "yes." If I ask "Do you think we should raise taxes by X%?", I can count on an equally large majority saying "No". I can give nearly a guaranteed outcome from my poll just by choosing whether to ask about the hoped-for outcome or the cost.

Bottom line: Each feature you add has a cost, and focusing on the outcome doesn't remove the cost. To design a usable language/library, you need to restrict features to the few that provide the greatest positive outcome for the lowest cost. In the case of the C++ committee, they've been pretty clear about a few things, and one is that most new features will barely be considered at all unless they enable fairly substantial improvements in code that uses the new feature and have extremely minimal cost, especially in terms of breaking any existing code.

\$\endgroup\$
2
  • \$\begingroup\$ That makes a lot of sense. When I asked I wasn't quite sure if there was some terrible glaring problem that it accidentally introduces that made it a bad idea or if it's just not important enough to be worth the complexity of adding. From the answers here it looks like it's the latter. \$\endgroup\$ Commented Nov 16, 2011 at 16:50
  • \$\begingroup\$ I don't see what cost is there, other from an additional way to do things. There is no performance overhead or cost. Do you still think this is true Jerry Coffin? I think not. \$\endgroup\$
    – KeyC0de
    Commented Nov 17, 2018 at 19:30
2
\$\begingroup\$

Its a nice trick. But I don't see what it buys you.

So you have a std::string object that you now use.

int main() {
  const std::string s = (std::ostringstream() << "hi" << 0).str();
  std::cout << s << std::endl;
}

In C++03 I would have a std::stringstream object that I now use (which can then be used in the same place as the std::string object).

int main()
{
  std::ostringstream message;
  message << "hi" << 0;       // one extra line.
  std::cout << message.str() << std::endl;
}

I don't see the advantage. Maybe if you can show the code being used in a larger context where it provides an advantage over std::stringstream.

\$\endgroup\$
3
  • \$\begingroup\$ The advantage is atomicity. Single calls to operator<< on std::cout and std::cerr are guaranteed to be atomic. You can get interleaving with output from other threads with std::cout << 'a' << 1 << false;, but not with std::cout << static_cast<const stringstream&>(std::stringstream() << 'a' << 1 << false).str();. \$\endgroup\$
    – uckelman
    Commented Apr 4, 2014 at 9:01
  • \$\begingroup\$ @uckelman: Yes. That is NOT what I am debating. If you see my code that part has not changed. My argument is that adding an extra list to make an explicit object (rather than a temporary) is more readable and only adds a single line. \$\endgroup\$ Commented Apr 4, 2014 at 14:15
  • \$\begingroup\$ This is buys ability to construct and take advantage of overloaded << that stringify complex structures without requiring being able to declare local variable in context where such variable cannot be declared. E.g. in constructor initialization lists so that you can construct class with a constant string field. That you cannot do if your syntax requires local variables (or doing even crazier theatrics like C-style function calls or lambdas. @Lightness_races_in_orbit's answer is perfect for such cases. \$\endgroup\$
    – LB2
    Commented May 3, 2021 at 21:53
2
\$\begingroup\$

Is there a good reason why this isn't provided by the standard library by default? Is it an oversight? Making this idiom legal code seems like it might offer a number of benefits.

Other than that the standard was already ~5 years delayed, and this little trick ultimately doesn't buy you much?

There are millions of cute little party tricks and convenience functions that can be written in C++. For one of them to get standardized, they have to offer a pretty significant benefit.

I see no harm in it though, and no reason why you shouldn't use it. But I don't think it's significant enough to warrant inclusion in the standard.

\$\endgroup\$
2
\$\begingroup\$

To add to the existing answers, this was already semi-possible:

const std::string s = static_cast<std::ostringstream&>(
   std::ostringstream() << "hi" << 0
).str();

Shove that in a #define and you're golden...

... except not really. Any operator<< implementations that are free functions rather than member functions will fail, because their LHS operand must be std::ostringstream& and, as a ref-to-non-const, this will not bind to the temporary std::ostringstream().

The almost-amusing result of the above example is that s will contain text like "0x804947c0", but swapping "hi" and 0 will result in the behaviour you'd expect ("0hi").

\$\endgroup\$
1
  • \$\begingroup\$ Ah, just what I was looking for, was trying to do the same thing, except I was casting static_cast<std::ostringstream> rather than to its lereference & and couldn't figure out why it wouldn't compile!. \$\endgroup\$
    – LB2
    Commented May 3, 2021 at 21:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.