How to replace a #define with a stringstream type method

98 Views Asked by At

I'm trying to replace a #define function like the following:

#define DEBUG(x) do { std::cout << x << std::endl; } while(0)

with a more RAII type error tracking object. This tracking object currently resembles this (*updated to include "intro" for clarity; and included stack dump logic from Ahmed AEK):

class tracker() {
public:
  tracker(std::string s) : intro(s) {}
  ~tracker() {
    if( this->stack.empty()) return;
    // dump_the_stack
    std::cout << "Messages from: " << intro << std::endl;
    for (const auto& item: stack) {
        std::cout << " * " << item;
    }
    std::cout << std::endl;
    // throw (abort)
  }
  void add( const std::string& message ) {
    stack.push_back( message );
  }
private:
  std::string intro;
  std::vector<const std::string> stack;
};

I would like to use this while keeping the existing #define syntax. For instance, I would like to have a method that allows the class user to maintain a syntax resembling one of these:

tracker.add( "value exceeded: " << value );

or even:

tracker << "value exceeded: " << value << std::endl;

Ideally, I believe the solution I want would look like:

tracker::add( ostringstream os ) {
  stack.push_back( os.str() + std::endl );
}

But it appears that there's no implicit conversion to ostringstream for the first case, and there's no way to determine the terminal << in an operator<<() case. Is there a simple solution here that I'm overlooking?


EDIT: for clarification below, and added "intro" to sample class above:

My ideal scenario is being able to use the syntax in either of these two test cases:

Case 1 (preferred):

int main()
{
    tracker t("test case 1");
    int trace = 0;

    t.add("hello world 1!" << " trace: " << ++trace);
    t.add("hello world 2!" << " trace: " << ++trace);
    return 0;
}

Case 2 (acceptable):

int main()
{
    tracker t("test case 1");
    int trace = 0;

    t << "hello world 1!" << " trace: " << ++trace;
    t << "hello world 2!" << " trace: " << ++trace;
    return 0;
}

The resultant output should look like:

    Messages from: test case 1
     * hello world 1! trace: 1
     * hello world 2! trace: 2
2

There are 2 best solutions below

0
rand'Chris On BEST ANSWER

Thanks to everyone who responded. Here is what I have ended up with. Though I would prefer to use the function-like interface to be more consistent with existing code, this does work well and addresses my immediate needs. The biggest nuisance is requiring an end-token for terminating the message.

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

class tracker {
public:
  tracker(std::string s) : intro(s) {};
  ~tracker() {
    std::cout << "Messages from: " << intro << std::endl;
    for (const auto& item: stack) {
        std::cout << " * " << item << std::endl;
    }
    std::cout << std::endl;
  }
  void add(std::string message ) {
    stack.push_back(std::move(message));
  }
  void operator<<(std::ostream& (*)(std::ostream&)) { // catch std::endl
    this->add(linebuf.str());
    linebuf.str(std::string());
  }
  template <typename T>
  tracker& operator<<(T&& item) {
    linebuf << std::forward<T>(item);
    return *this;
  }

private:
  std::string intro;
  std::ostringstream linebuf;
  std::vector<std::string> stack;
};

int main()
{
    tracker t("test1");
    int trace = 0;

    t << "hello world 1!" << " trace: " << ++trace << std::endl;
    t << "hello world 2!" << " trace: " << ++trace << std::endl;
    return 0;
}

Here's a live example, maybe...

6
Ahmed AEK On

you can just print the output in the destructor of a temporary as follows.

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

class tracker {
public:
  tracker() = default;
  ~tracker() {
    for (const auto& item: stack)
    {
        std::cout << item;
    }
    std::cout << std::endl;
  }
  void add(std::string message ) {
    stack.push_back(std::move(message));
  }
private:
  std::vector<std::string> stack;
};

tracker&&  operator<<(tracker&& tr, std::string item)
{
    tr.add(std::move(item));
    return std::move(tr);
}

#define DEBUG(X) tracker{} << X

int main()
{
    DEBUG("hello" << " " << "world!");
}

an unfortunate side-effect is the need to do return std::move(tr); and since the lifetime of this object extends up to the closing semi-colon, there is no UB or leaks here.

to be able to use any user-defined ostream overload you can create a free function that uses a stringstream as follows.

template <typename T>
tracker&& operator<<(tracker&& tr, T&& item)
{
    if constexpr (std::is_convertible_v<T,std::string>)
    {
        tr.add(std::forward<T>(item));
    }
    else
    {
        std::ostringstream s;
        s << std::forward<T>(item);
        tr.add(s.str());
    }
    return std::move(tr);
}