1

For an API that I am working on, I want to allow the user to insert custom objects into an ostream, but these objects have no meaning on their own, and are too memory constrained to include an additional pointer or reference for context. (Think tens of millions of 16-/32-/48-bit objects in an embedded system with limited memory.)

Suppose the user initializes the underlying context, and looks up one of these objects:

DDB ddb("xc5vlx330t");
Tilewire tw = ddb.lookUpTilewire("DSP_X34Y0", "DSP_IMUX_B5_3");
...
std::cout << complexDataStructure;

In an entirely different scope, possibly nested far away from the user's explicit code, we may need to insert the object into an ostream, with ddb unavailable.

os << tw;

The actual value encapsulated by tw is 97,594,974, but the desired output is this:

DSP_IMUX_B5_3@[263,84] DSP "DSP_X34Y0" (1488@77406)

In order for this to work, the appropriate insertion operator would need access to ddb, but it cannot rely on static or global variables or functions (for multithreading reasons). What I'd like to do is allow the user to request and use a stream wrapper kind of like this:

ostream& wrappedCout = ddb.getWrappedOstream(std::cout);

The returned subclass of ostream would include a reference to ddb for use by special stream inserters that needed it, and a reference to the original stream—std::cout in this case—where it would forward all of its output.

Unfortunately, the inheritance or composition schemes that I have come up with are messy to code up (not an enormous concern), and possibly problematic for the user (a much larger concern). Any suggestions on how to elegantly make ddb available to insertion operators? I am marginally aware of boost.Iostreams, but not sure that it will help me out here.

Neil Steiner
  • 729
  • 1
  • 7
  • 17
  • Can't you use thread-local storage for this? Since you are concerned about threading, you probably have a threading library providing some kind of thread locals. –  Jun 24 '10 at 22:55
  • Thread-local storage is an interesting idea, but in general multiple DDB objects might coexist within the same thread, so I'd still prefer to wrap the context into the stream. – Neil Steiner Jun 24 '10 at 23:01

4 Answers4

2

Write a custom stream manipulator that stores a reference to ddb using the iword/pword mechanism. Here is an example, you'd need to add locking around the iwork_indexes map in a multithreaded program.

class dbb
{
public:
    explicit dbb(int value) : m_value(value) {}
    int value() const { return m_value; }
private:
    int m_value;
};

class dbb_reliant_type
{
public:
    dbb_reliant_type(const std::string& value) : m_value(value) {}
    const std::string& value() const { return m_value; }
private:
    std::string m_value;
};

typedef std::map<std::ostream*, int> iword_map;
iword_map iword_indexes;

inline int get_iword_index(std::ostream& os)
{
    iword_map::const_iterator index = iword_indexes.find(&os);

    if(index == iword_indexes.end())
    {
        std::pair<iword_map::iterator, bool> inserted = iword_indexes.insert(std::make_pair(&os, os.xalloc()));
        index = inserted.first;
    }

    return index->second;
}


inline std::ostream& operator<<(std::ostream& os, const dbb& value)
{
    const int index = get_iword_index(os);

    if(os.pword(index) == 0)
        os.pword(index) = &const_cast<dbb&>(value);

    return os;
}

std::ostream& operator<<(std::ostream& os, const dbb_reliant_type& value)
{
    const int index = get_iword_index(os);
    dbb* deebeebee = reinterpret_cast<dbb*>(os.pword(index));
    os << value.value() << "(" << deebeebee->value() << ")";
    return os;
}

int main(int, char**)
{
    dbb deebeebee(5);
    dbb_reliant_type variable("blah");
    std::cout << deebeebee << variable << std::endl;
    return 0;
}
Joe
  • 41,484
  • 20
  • 104
  • 125
Gary
  • 5,642
  • 1
  • 21
  • 41
  • Here's an example I googled up, I haven't look at it in detail but it should give you enough to go on. http://www.unc.edu/depts/case/pgi/pgC++_lib/stdlibug/str_5412.htm – Gary Jun 25 '10 at 00:09
  • in the future, you can edit your own reply to include more expansive answers (e.g. the URL), example code, etc. – Joe Jun 25 '10 at 00:26
  • You're right. I just found that same link, and verified that it'll work. I'll post the code in a little bit and also give you credit for the answer. – Neil Steiner Jun 25 '10 at 00:44
  • Sorry, Gary: I'm a newbie, so I can't vote you up, but I did accept your answer. – Neil Steiner Jun 25 '10 at 01:24
1

I'm not entirely sure if I understand what can be accessed at what time and what can and can't change, but....can you do something like this

struct TilewireFormatter {
    DDB *ddb;
    TilewireFormatter(DDB* d) : ddb(d) {}

    print(std::ostream& out, const Tilewire& obj) {
        // some formatting dependent on ddb
        out << obj;
    }
};

and replace out << tw; with formatter.print(out, tw);

then not provide any sort of << operator overload for Tilewire and pass an instance of TilewireFormatter around that's used to format them based on what ddb is?

choobablue
  • 754
  • 6
  • 6
  • +1 -- just keep in mind that ddb must be kept alive somehow with this code. – Billy ONeal Jun 24 '10 at 23:04
  • @choobablue Unfortunately there isn't any good way to pass objects around, and this approach wouldn't allow the user to rely on regular stream semantics (this is an API, so the cost of adoption needs to remain low). The nice things about wrapping ddb inside the ostream is that operator<<(ostream& os, ...) gets a reference to it for free, and I could use a dynamic cast to get at the context. – Neil Steiner Jun 24 '10 at 23:20
  • @Billy ONeal The lifespan of the DDB object can safely be assumed to encompass any of these calls. – Neil Steiner Jun 24 '10 at 23:21
  • I didn't have any idea as to the scope or copy semantics of DDB so I just did a 'wrong way' as it was simple. If it was me, I would probably make copies of the relevant members of DDB and store those in the formatter instead of relying on DDB. And if you want it to have regular stream semantics you could have TilewireFormatter return a temporary object that does define operator<< then you get more like out << formatter(tw); if that's still too much, you could look into using .imbue(), I don't really know much about it, boost::date_time uses it, I think it decorates the stream somehow – choobablue Jun 24 '10 at 23:28
  • @choobablue I appreciate the suggestions, but I think they're getting further away from where I'm trying to go. DDB consists of multiple MB of not-easily-separable data, so I can't really grab its members. As for using an operator<<() with TilewireFormatter, someone still has to pass the TilewireFormatter around, because the only thing coming into a number of these functions is the intended ostream&. And as for imbue(), that's an interesting thought, though it would be a complete misuse of the function. I'll keep it in minds in case all of my other options run out. – Neil Steiner Jun 24 '10 at 23:53
  • @Neil Steiner: Well you'd have to pass around a reference to your stream in the first place, so you're really not saving anything by eschewing that reference. – Billy ONeal Jun 25 '10 at 00:17
  • @Billy ONeal You're right that the user would still be passing something around, but it's something that they're very familiar and comfortable with, and it follows normal stream semantics. Again, since this is an API, I really don't want to discourage new users from trying it out. As you can see from Gary's answer and my answer, I believe we found a cleaner way. – Neil Steiner Jun 25 '10 at 01:22
1

I'm new at this, so in case providing my own answer gets in the way of me sharing the credit with Gary, well, Gary pointed out what I had just stumbled upon moments before through the same reference: Stream Storage for Private Use: iword, pword, and xalloc

#include <iostream>

// statically request a storage spot that can be associated with any stream
const int iosDdbIndex = std::ios_base::xalloc();

class DDB {
public:
    // give the stream a pointer to ourselves
    void bless(std::ostream& os) { os.pword(iosDdbIndex) = this; }
    // provide a function that the insertion operator can access
    int getSomething(void) { return 50; }
};

class Tilewire {
    friend std::ostream& operator<< (std::ostream& os, Tilewire tilewire);
    // encapsulate a dummy value
    int m;
public:
    // construct the Tilewire
    Tilewire(int m) : m(m) {}
};

std::ostream& operator<< (std::ostream& os, Tilewire tilewire) {
    // look up the pointer to the DDB object
    DDB* ddbPtr = (DDB*) os.pword(iosDdbIndex);
    // insert normally, and prove that we can access the DDB object's methods
    return os << "Tilewire(" << tilewire.m << ") with DDB param " << ddbPtr->getSomething();
}

int main (int argc, char * const argv[]) {
    DDB ddb;
    ddb.bless(std::cout);
    std::cout << Tilewire(0) << std::endl;
    return 0;
}
Neil Steiner
  • 729
  • 1
  • 7
  • 17
0

Rather than fudging around and trying to find a way to pass contextual information while using the insertion operator, I suggest you make something like a print method like choobablue suggests. It's a nice and simple solution and anything fancier is probably more trouble than it's worth.

I also find it odd that you choose iostreams for an embedded system. They're one of the most bloated parts of the C++ standard library (not just by implementation, but by design) and if you are working on an embedded system, you could just as well roll your own alternative of this (still based on the basic design of iostreams) and can probably do it just as quickly as trying to use iostream effectively and across multiple threads.

stinky472
  • 6,737
  • 28
  • 27
  • Why the IOStreams FUD? I have yet to see a case where any speed limitation was an issue because any I/O device you're referencing with it is usually the bottleneck. Use the various streambuf classes if you need to be closer to the metal. – Billy ONeal Jun 24 '10 at 23:34
  • 1
    @stinky472 I think you may agree that the answer above is actually quite elegant from the user's perspective. But I'm with you on iostreams and embedded systems: The more complete story is that some users may need to run this embedded, and for those, I'm going to try to separate out the iostreams dependencies. In fact, the solution above will go a long way towards helping me segregate things. And for people running on regular systems, simplicity for the user is highly valued. – Neil Steiner Jun 25 '10 at 01:30
  • @Billy not speed so much, but for embedded systems with limited resources, I'm talking more about bloat. Even Sutter discusses this quite in depth and the mistakes the standard committee made by simply adopting legacy I/O streams with little changes. The combination of virtual methods and templates, for instance, leads to a lot of bloat. It's not uncommon to increase executable size dramatically just by having a hello world program that uses cout. – stinky472 Jun 25 '10 at 23:18
  • The user already mentioned he's on an embedded system with limited memory. – stinky472 Jun 25 '10 at 23:20