Home > OS >  c function that accepts ostringstream like arguments?
c function that accepts ostringstream like arguments?

Time:11-09

Is there a way in C to define a function so that I could do things like this:

foo( "Error on line " << iLineNumber );
foo( "name " << strName << " is Invalid" );

as it is now, I'm having to declare an ostringstream object before I call the foo function like this:

std::ostringstream strStream1;
ostringstream1 << "Error on line " << iLineNumber;
foo( ostringstream1 );

std::ostringstream strStream2;
ostringstream2 << "name " << strName << " is Invalid";
foo( ostringstream2 );

CodePudding user response:

The compiler is not able to deduce that you want your expression to be converted to std::ostringstream. You can get around this by explicitly providing one to the arguments, which you can do inline with the function :

#include <sstream>
#include <string>

void foo(std::ostringstream osstr)
{
    // Use the stream
}

int main()
{
    const int iLineNumber = 10;
    const std::string strName = "file.cpp";

    foo( std::ostringstream{} << "Error on line " << iLineNumber );
    foo( std::ostringstream{} << "name " << strName << " is Invalid" );
}

However, I recommend using a parameter pack and fold expression instead to forward the arguments :

#include <sstream>
#include <string>
#include <utility>

// Parameter, variable number of arguments of various types
template<class ... T>
void foo(T&& ... args)
{
    std::ostringstream stream;
    // Fold expression, for each argument in args, add it to the stream
    (stream << ... << std::forward<T>(args));

    // Use the stream
}

int main()
{
    const int iLineNumber = 10;
    const std::string strName = "file.cpp";

    foo( "Error on line ", iLineNumber );
    foo( "name ", strName, " is Invalid" );
}

CodePudding user response:

The immediate issue is that the string and number below

oss << "String " << number;

are not "ostringstream arguments". They're each parameters to different overloads of the std::ostream& operator<< (std::ostream&, T) family. The whole statement expands to

operator<<(operator<<(oss, "String "), number);

and the tokens "String " << number are never evaluated as an expression. There is no suitable overload of operator<<, and even if there were, there's no reason to expect it would create the ostringstream you want.


There are three ways to do roughly what you want, and François has already shown two:

  1. Manually create an anonymous ostringstream

    Not beautiful, no extra work now, but extra work at every call site

  2. Make your function a variadic template (or write a variadic wrapper) to format the arguments into an ostringstream for you

    Easy to implement, but doesn't use the << syntax you seem so keen on

  3. Write a streaming proxy sentinel type, which would look like this at the call site:

     foo() << "Error on line " << iLineNumber;
    

    This is a load of effort just to preserve the syntactic style, but might occasionally be justified.

    The object would contain an ostringstream and have a templated operator<< allowing it to format any type.

    I called it a "sentinel" because it also needs to call the original foo() function with that formatted string, from its destructor.

    This is actually a pretty bad idea if the inner foo() function might throw.

CodePudding user response:

@Useless has give a few possibilities. I'll throw one more into the mix to see if you find it interesting.

Right now, your code that works has something like:

std::ostringstream buffer;
buffer << "whatever";
foo(buffer);

It's fairly easy to turn that around, so to speak, to get something along these lines:

std::ostringstream buffer;
buffer << "whatever" << exec(foo);

...and have that exec invoke foo with the current contents of the stringstream:

class exec {
    void (*func)(std::string const &);
public:
    exec(void (*func)(std::string const &)) : func(func) {}

    friend std::ostream &operator<<(std::ostream &os, exec const &e) { 
        std::ostringstream &oss = dynamic_cast<std::ostringstream &>(os);
        e.func(oss.str());
        return os;
    }
};

Here's a trivial (but complete) working example:

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

class exec {
    void (*func)(std::string const &);
public:
    exec(void (*func)(std::string const &)) : func(func) {}

    friend std::ostream &operator<<(std::ostream &os, exec const &e) { 
        std::ostringstream &oss = dynamic_cast<std::ostringstream &>(os);
        e.func(oss.str());
        return os;
    }
};

void foo(std::string const &s) {
  std::cout << s;
}

int main() {
  std::ostringstream os;

  os << "This is some stuff: " << 1 << " " << 3.14 << exec(foo);
}

...which produces pretty much what you'd expect:

This is some stuff: 1 3.14
  • Related