Boost logo

Boost :

From: Maciej Sobczak (maciej_at_[hidden])
Date: 2002-10-24 02:21:26


Hi Boosters,

My previous attempt to the xalloc & pword wrapper was a complete
failure. I was misled by the VC++6SP5 compiler, which proved to be bad
authority... I've reviewed te code with the Standard and some better
compiler.

Now I would like to present to you two approaches to the problem.

1. State associated with the type (class streamstate).
The slot identifier is a static property of the particular
instantiation. It allows to use the stream state without the need to
keep around any object wrapping the knowledge about the slot. Can be
handy for formatting uses, like in the example code.

2. State associated with object (class streamstate_value, for the lack
of better name - any ideas?).
The slot identifier is a property of some real object with value
semantics, that can be a standalone object, member, mix-in or whatever.
It encapsulates the knowledge about the slot dentifier and allows to use
many objects of the same type in the stream state.

I would like to know your opinion about these two approaches.

The code compiles on g++-3.2.
VC++ 6 and 7 gave up.

Cheers,

---
Maciej Sobczak
http://www.maciejsobczak.com/

// a test program for the streamstate utility

#include "streamstate.h"
#include <iostream>

struct brackets
{
    // default state
    brackets()
        : left_('('), right_(')') {}

    // arbitrary state
    brackets(char l, char r)
        : left_(l), right_(r) {}

    char left_;
    char right_;
};

class mypair
{
public:
    mypair(int a, int b) : a_(a), b_(b) {}

    std::ostream & print(std::ostream &os) const
    {
        // get the stream state (maybe default) and use it
        brackets &b = streamstate<brackets>::get(os);

        return os << b.left_ << a_ << ", " << b_ << b.right_;
    }

private:
    int a_;
    int b_;
};

std::ostream & operator<<(std::ostream &os, const mypair &p)
{
    return p.print(os);
}

// this example shows how to store and retrieve state
// associated with some type
// any time we refer to some type, we get the same state slot
void example1()
{
    std::cout << "example 1 - state associated with type" << std::endl;
    
    mypair p(1, 2);

    // print with default brackets
    std::cout << p << std::endl;

    // store in the stream new brackets
    std::cout << streamstate<brackets>(brackets('[', ']'));

    // print with new brackets
    std::cout << p << std::endl;

    // again, change the brackets
    std::cout << streamstate<brackets>(brackets('{', '}'));

    // and use them
    std::cout << p << std::endl;

    // we can also store long values,
    // but only one per stream

    // print the default long
    std::cout << streamstate<long>::get(std::cout) << std::endl;

    // set new long value
    std::cout << streamstate<long>(7);

    // the same, but different syntax
    streamstate<long>::get(std::cout) = 7;

    // print new long value
    std::cout << streamstate<long>::get(std::cout) << std::endl;
}

// this example shows how to store and retrieve state
// associated with some "third-party" object
// in fact, the state object encapsulates the slot identifier
// this way we can store many different state values of the same type
void example2()
{
    std::cout << "example 2 - state associated with object" << std::endl;
    
    streamstate_value<brackets> state1;
    streamstate_value<brackets> state2;
    
    // we can store many objects of the same type
    
    state1.get(std::cout) = brackets('[', ']');
    state2.get(std::cout) = brackets('{', '}');
    
    // and of course retrieve them
    
    std::cout << state1.get(std::cout).left_ << state1.get(std::cout).right_;
    std::cout << std::endl;
    std::cout << state2.get(std::cout).left_ << state2.get(std::cout).right_;
    std::cout << std::endl;
    
    // we can also store many longs in a stream
    
    streamstate_value<long> state3;
    streamstate_value<long> state4;
    
    state3.get(std::cout) = 8;
    state4.get(std::cout) = 9;
    
    std::cout << state3.get(std::cout) << std::endl;
    std::cout << state4.get(std::cout) << std::endl;
}

int main()
{
    example1();
    example2();
    
    return 0;
}


// Copyright Maciej Sobczak, 2002
//
// Permission to use, copy, modify, and distribute this software for any
// purpose is hereby granted without fee, provided that this copyright and
// permissions notice appear in all copies and derivatives.
//
// This software is provided "as is" without express or implied warranty.

#ifndef STREAMSTATE_H_INCLUDED
#define STREAMSTATE_H_INCLUDED

#include <iostream>
#include <new>

// State should be:
// default-constructible
// copy-constructible
// assignable

template
<
    class State,
    class charT = char,
    class traits = std::char_traits<charT>
>
class streamstate
{
public:
    // construct with the default state value
    streamstate() {}

    // construct with the given stream value
    streamstate(const State &s) : state_(s) {}

    // modifies the stream
    // the return value is not really useful,
    // it has to be downcasted to the expected stream type
    std::basic_ios<charT, traits> &
    modify(std::basic_ios<charT, traits> &ios) const
    {
        void *&p = state_slot(ios);

        if (ios.bad())
            throw std::bad_alloc();
        if (p == NULL)
        {
            // copy existing state object if this is new slot
            p = new State(state_);
            ios.register_callback(state_cleanup, 0);
        }
        else
            *static_cast<State*>(p) = state_;
        
        return ios;
    }

    static State & get(std::basic_ios<charT, traits> &ios)
    {
        void *&p = state_slot(ios);

        if (ios.bad())
            throw std::bad_alloc();
        if (p == NULL)
        {
            // create default state if this is new slot
            p = new State;
            ios.register_callback(state_cleanup, 0);
        }

        return *static_cast<State*>(p);
    }

private:
    static void state_cleanup(std::ios_base::event e,
        std::ios_base &ios, int)
    {
        if (e == std::ios_base::erase_event)
        {
            // safe delete if state_slot fails
            delete static_cast<State*>(state_slot(ios));
        }
    }

    static void *& state_slot(std::ios_base &ios)
    {
        static int index = std::ios_base::xalloc();
        void *&p = ios.pword(index);

        // note: if pword failed,
        // then p is a valid void *& initialized to 0
        // (27.4.2.5/5)

        return p;
    }
    
    State state_;
};

// partial specialization for iword functionality
template
<
    class charT,
    class traits
>
class streamstate<long, charT, traits>
{
public:
    // construct with the default state value
    streamstate() {}

    // construct with the given stream value
    streamstate(long s) : state_(s) {}

    // modifies the stream
    // the return value is not really useful,
    // it has to be downcasted to the expected stream type
    std::basic_ios<charT, traits> &
    modify(std::basic_ios<charT, traits> &ios) const
    {
        long &s = state_slot(ios);
        s = state_;
        
        return ios;
    }

    static long & get(std::basic_ios<charT, traits> &ios)
    {
        return state_slot(ios);
    }

private:
    static long & state_slot(std::basic_ios<charT, traits> &ios)
    {
        static int index = std::ios_base::xalloc();
        long &s = ios.iword(index);
        if (ios.bad())
            throw std::bad_alloc();

        return s;
    }

    long state_;
};

// convenience inserter for ostream classes
template
<
    class State,
    class charT,
    class traits
>
std::basic_ostream<charT, traits> &
operator<<(std::basic_ostream<charT, traits> &os,
    const streamstate<State> &s)
{
    s.modify(os);
    return os;
}

// the alternative if there is a need to have
// many different state values of the same type

template
<
    class State,
    class charT = char,
    class traits = std::char_traits<char>
>
class streamstate_value
{
public:

    streamstate_value()
        : index_(-1)
    {
    }

    State & get(std::basic_ios<charT, traits> &ios)
    {
        void *&p = state_slot(ios, index_);

        if (ios.bad())
            throw std::bad_alloc();
        if (p == NULL)
        {
            // create default state if this is new slot
            p = new State;
            ios.register_callback(state_cleanup, index_);
        }

        return *static_cast<State*>(p);
    }

private:
    static void state_cleanup(std::ios_base::event e,
        std::ios_base &ios, int index)
    {
        if (e == std::ios_base::erase_event)
        {
            // safe delete if state_slot fails
            delete static_cast<State*>(state_slot(ios, index));
        }
    }

    static void *& state_slot(std::ios_base &ios, int & index)
    {
        if (index < 0)
        {
            // first index usage
            index = std::ios_base::xalloc();
        }
           
        void *&p = ios.pword(index);

        // note: if pword failed,
        // then p is a valid void *& initialized to 0
        // (27.4.2.5/5)

        return p;
    }

    int index_;
};

// partial specialization for iword functionality
template
<
    class charT,
    class traits
>
class streamstate_value<long, charT, traits>
{
public:
    // construct with the default state value
    streamstate_value()
        : index_(-1)
    {
    }

    long & get(std::basic_ios<charT, traits> &ios)
    {
        if (index_ < 0)
        {
            // first index usage
            index_ = std::ios_base::xalloc();
        }
           
        long &s = ios.iword(index_);
        if (ios.bad())
            throw std::bad_alloc();

        return s;
    }

private:
    long index_;
};

#endif // STREAMSTATE_H_INCLUDED


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk