0

I want to write a macro that takes as its only argument a list of std::ostream& operator<< concatenated objects and passes the consolidated string as a single std::string object to a function. The ability to pass the consolidated string to a function is key; in the example below I am aware that the example itself could be rewritten to work simply by defining the macro to ERR_MSG(inputs) std::cout << "ERROR: " << inputs, but sending the output to std::cout is not the goal, it's just the test objective I chose for the example.

I'm using GCC 4.1.2 (Red Hat 4.1.2-52) and upgrading it is not an option. Here's a very boiled-down version of what I've tried:

#include <sstream>
#include <iostream>

#define ERR_MSG(inputs) errMsg(std::ostringstream().str())           // 1
#define ERR_MSG(inputs) errMsg((std::ostringstream()<<inputs).str()) // 2
<aReturnType> errMsg(const std::string& msg)                                 // use with 1 & 2
{
    std::cout << "\nERROR: " << msg << "\n\n";
    return <someObjectCreatedBasedOnTheInput>;
}

#define ERR_MSG(inputs) errMsg(std::ostringstream()<<inputs)         // 3
<aReturnType> errMsg(const std::ostringstream& msg)                           // use with 3
{
    std::cout << "\nERROR: " << msg.str() << "\n\n";
    return <someObjectCreatedBasedOnTheInput>;
}

int main()
{
    ERR_MSG("A number: " << 24 << ", a char: " << 'c' << ", that's all!");
}

Macro #1 compiles, but of course prints nothing but "" for the message. Neither macros 2 & 3 compile, with the following errors:

#define ERR_MSG(inputs) errMsg((std::ostringstream()<<inputs).str()) // 2
error: ‘struct std::basic_ostream<char, std::char_traits<char> >’ has no member named ‘str’

#define ERR_MSG(inputs) errMsg(std::ostringstream()<<inputs)         // 3
no matching function for call to ‘errMsg(std::basic_ostream<char, std::char_traits<char> >&)’
    note: candidates are: char* errMsg(const std::string&)
    note:                 char* errMsg(const std::ostringstream&)

I am not interested in how I could rewrite this without macros; I can do that quite easily myself.

=== UPDATE: === I forgot to mention that in its real use case, the function called by the macro returns an object that may be used by the caller of the macro. That invalidates any macro implementations that cannot be implemented in a single expression whose result is the returned type of the function called by the macro. The "do nothing" implementation of the macro (for release builds) will simply pass an empty std::string to the function regardless of what the "inputs" are. Sorry for not mentioning that earlier.

phonetagger
  • 7,701
  • 3
  • 31
  • 55
  • `std::ostringstream::operator<<` returns a `std::ostream &`. – chris Sep 28 '12 at 21:32
  • Hmm. OK. Any ideas on how else this objective might be achieved? (using a macro and <<'s to create a std::string that gets passed to a function)? – phonetagger Sep 28 '12 at 21:35
  • I believe there's a nice function somewhere in `std::ostream` that you could use with a `const std::ostream &` overload to print it. It might've been `rdbuf`, but I forget. – chris Sep 28 '12 at 21:39

6 Answers6

3

Your current problem is that all of the various operator<< functions return an ostream&, not an ostringstream&. You can solve that with a simple cast:

#define ERR_MSG(inputs) errMsg((static_cast<std::ostringstream&>(std::ostringstream().flush() << inputs)).str())

The flush is needed because std::ostringstream() is a temporary. Therefore, you can't call functions on it that take an lvalue reference (ie: std::ostream&). Functions like most operator<< variants. All the flush call does is return the this pointer as an lvalue reference.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • "...you can't call functions on it that take an lvalue reference... ...All the `flush` call does is return the `this` pointer as an lvalue reference." Hmmm. Someday I hope to understand what that means. But it seems like if that's all flush() does, then the same thing ought to be accomplishable with some sort of cast+dereference combination? – phonetagger Sep 29 '12 at 14:17
  • @phonetagger: Perhaps, but `flush` is shorter. ;) – Nicol Bolas Sep 29 '12 at 14:21
  • There are two key points to making this work, first the "simple cast" from ostream& to ostringstream&, and second, the .flush() call, which was offered by "Rob" with the funny character at the end of his name (which in whatever character set my browser is using shows up as just a box). Without both of those, the macro doesn't work. Nicol originally posted point 1 first, then Rob posted with the .flush() which finally made the whole thing work. I wish I could split the acceptance between you both, but since Nicol gave me an explanation for why flush() is necessary, I'll pick this answer. – phonetagger Sep 29 '12 at 14:27
1

If you are willing to use some GCC extension, you could declare an actual ostringstream inside the macro in a block, so that the .str() method can be used without casting:

#define ERR_MSG(inputs) \
    do { std::ostringstream _s_; _s_<<inputs;errMsg(_s_.str()); } while(false)

Demo: http://ideone.com/clone/y56lc

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Just get rid of those outer parens and it will work on any compiler! :) You're then just defining a regular block. It doesn't matter if the outer scope contains something named `_s_` because lookup of `_s_` will always find the innermost-declared one (possibly barring weird Koenig lookup corner cases); the `_s_` object is destroyed and cleaned up at the closing `}`! – j_random_hacker Sep 28 '12 at 21:53
  • 1
    @j_random_hacker Be aware of case: `int _s_ = 7; ERR_MSG(__s__)`. You will get nothing instead of `7`... Hiding external names is not always a good thing. And without this gcc extensions this will not compile: `if (true) ERR_MSG(7); else ERR_MSG(6);`. See my answer for portable solution. – PiotrNycz Sep 28 '12 at 21:57
  • @j_random_hacker. Hmm right. Somehow I wanted to unnecessarily keep the macro as an expression, although a statement is fine. – kennytm Sep 28 '12 at 22:01
  • @PiotrNycz: I'm confused as to why you doubled the underscores in `ERR_MSG(__s__)`. A typo? If you just meant `ERR_MSG(_s_)` then you make a good point. – j_random_hacker Sep 28 '12 at 22:05
  • @j_random_hacker Of course TYPO, Just doing some other thing in parallel and not very good in counting underscores ;) – PiotrNycz Sep 28 '12 at 22:17
1

Use do { } while (false) idiom to make a few lines macro.

#define ERR_MSG(inputs) \
  do { \
      std::ostringstream osERR_MSG; \
      osERR_MSG << inputs; \
      errMsg(osERR_MSG.str()); \
  } while (false)


int main() {
   if (1) ERR_MSG("A number: " << 24 << ", a char: " << 'c' << ", that's all!");
   else return 0;
}

The reason I made such strange name osERR_MSG is to avoid as much as possible cases like this:

int osERR_MSG = 7;
ERR_MSG(osERR_MSG);
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
1
#include <sstream>
#include <iostream>

#define ERR_MSG(inputs) errMsg(std::ostringstream().flush()<<inputs)
int errMsg(std::ostream& os)
{
    std::ostringstream& oss(static_cast<std::ostringstream&>(os));
    const std::string& str(oss.str());
    std::cout << "\nERROR: " << str << "\n\n";
    return str.length();
}

int main()
{
    int i = ERR_MSG("A number: " << 24 << ", a char: " << 'c' << ", that's all!");
   std::cout << i << "\n";
}
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Uhm.... This seems to work, but is casting the address of os to a std::ostringstream pointer and dereferencing it to initialize the oss reference variable safe? For more than just GCC? – phonetagger Sep 28 '12 at 22:20
  • This seems to work too... `const std::string& str((static_cast(os)).str());` ....but is that safe (same question as above)? – phonetagger Sep 28 '12 at 22:25
  • 1
    Why is the .flush() necessary before the < – phonetagger Sep 28 '12 at 22:31
  • Yes, casting from `ostream*` to `ostringstream*` is valid (as is casting from `ostream&` to `ostringstream&`), as long as the object is question really is an `ostringstream`. The `.flush()` is what makes the macro work. – Robᵩ Sep 28 '12 at 22:37
  • I tried to cast the `ostream&` to an `ostringstream&`, but couldn't get it to work right initially. I don't know what I messed up. I'll fix the example. – Robᵩ Sep 28 '12 at 22:39
  • Your answer is now essentially the same as NicolBolas/ChrisDodd's answer (once Nicol updated it with the .flush()), but without the .flush() it just doesn't work. Other than "The .flush() is what makes the macro work," can you explain why it doesn't work without it? – phonetagger Sep 28 '12 at 22:44
0

I'd not create an std::ostringstream but rather have a function called from the destructor of a class derived from std::ostream. Here is an example of this approach:

#include <sstream>
#include <iostream>

void someFunction(std::string const& value)
{
    std::cout << "someFunction(" << value << ")\n";
}

void method(std::string const& value)
{
    std::cout << "method(" << value << ")\n";
}

class FunctionStream
    : private virtual std::stringbuf
    , public std::ostream
{
public:
    FunctionStream()
        : std::ostream(this)
        , d_function(&method)
    {
    }
    FunctionStream(void (*function)(std::string const&))
    : std::ostream(this)
    , d_function(function)
    {
    }
    ~FunctionStream()
    {
        this->d_function(this->str());
    }
private:
    void (*d_function)(std::string const&);
};

int main(int ac, char* av[])
{
    FunctionStream() << "Hello, world: " << ac;
    FunctionStream(&someFunction) << "Goodbye, world: " << ac;
}

The example use doesn't use a macro but this can be wrapped easily around the above use of FunctionStream(). Note, that in a macro you probably want to make sure that the type seen by the user of the macro is of type std::ostream& rather than a temporary type so it can be used directly with user defined output operators. To this end you should have an insertion for one of the types directly supported by std::ostream which doesn't have any effect but returns an std::ostream&, for example:

#define SomeMacro(output) FunctionStream(&someFunction) << "" << output
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I think that's kinda missing the point. He doesn't want to call any function with the string; he wants to be able to use ERR_MSG as a *parameter* to the function. There could be other function parameters too. – Nicol Bolas Sep 28 '12 at 21:48
  • @NicolBolas: based on his example he _does_ want to get a `std::string const&`! It seems the goal is to create macro which can be used to remove, e.g., debug output based on some macro definitions. – Dietmar Kühl Sep 28 '12 at 21:50
  • I have a feeling this is a good answer but it's a little more involved than Rob's/Nicol's/ChrisDodd's. – phonetagger Sep 28 '12 at 22:34
0

Reinstating Nicol's answer as its the best so far:

Your current problem is that all of the various operator<< functions return an ostream&, not an ostringstream&. You can solve that with a simple cast:

#define ERR_MSG(inputs) errMsg((static_cast<std::ostringstream&>(std::ostringstream().flush() << inputs)).str())

Of course, this still has the problem (like all the answers here) that something like

ERR_MSG(x ? "x is true" : "x is false")

will misbehave in an odd and confusing manner.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • It's a fine answer except that *it doesn't work*. That's why I deleted it. It prints the first string as a pointer rather than a `char*`. – Nicol Bolas Sep 28 '12 at 22:31
  • Notice my comment below Rob's post. Your answer DOES work if you add the .flush() between the std::ostringstream() and < – phonetagger Sep 28 '12 at 22:32
  • Re: "...this still has the problem (...) that something like `ERR_MSG(x ? "x is true" : "x is false")` will misbehave in an odd and confusing manner.": It doesn't even compile for me. But if I wrap the ternary in parentheses, it does compile, and it does output what one would expect. – phonetagger Sep 28 '12 at 22:39
  • @NicolBolas - Yes, exactly. I wish someone would explain what the heck the .flush() does that makes it work and why omitting it doesn't work. – phonetagger Sep 28 '12 at 22:40
  • @phonetagger: I reinstated my answer with an explanation of why the `flush` is needed. – Nicol Bolas Sep 28 '12 at 23:39