Search code examples
c++chainingostreaminsertion

Need to make context available to C++ ostream insertion operators


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.


Solution

  • 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;
    }