Wednesday, April 15, 2015

Creating iomanip for a class - the easy way

Now creating custom io manipulator for your classes can be damn annoying.. The trick to building them is to install and manage a handler for the custom manipulator in the pword array of the ostream. I have hacked this little CustomManip class that allows you to quickly install a lambda function as the a stream manipulator to select from or parameterize the various print methods that you have in play for your class. The only requirement is that each class have at least a " void printDefault(std::ostream& os) const" method that the default print method.

PS: Also there is one little bug with the code.. it does install the streamEvent responder several times.. but what a blog post without a bug or 2..

Anyway heres the code:
//g++ -g --std=c++11 custom_class_manip.cpp

#include <iostream>
#include <ios>
#include <sstream>
#include <functional>

template <typename TYPE>
class CustomManip
{
public:
    typedef std::function<void(std::ostream&, const TYPE&)> ManipFunc;

    // installation helper for a manipulator with params
    class Installer
    {
        ManipFunc func_;

    public:
        Installer(ManipFunc func) :
            func_(func)
        {}

        inline friend
        std::ostream& operator<<(std::ostream& os,
                                 const Installer& installer )
        {
            CustomManip<TYPE>::install(os,
                                       installer.func_);
            return os;
        }
    };

private:
    struct CustomManipHandle
    {
        ManipFunc func_;

        CustomManipHandle()  { std::cout << "new handle at " << this << "\n"; }
        ~CustomManipHandle() { std::cout << "del handle at " << this << "\n"; }
    };

    static int handleIndex()
    {
        // the id for this Custommaniputors params
        // in the os_base parameter maps
        static int index = std::ios_base::xalloc();
        return index;
    }

public:

    // for parameterized manipulator installations
    static Installer make_installer(ManipFunc func)
    {
        return Installer(func);
    }

    // for unparameterized manipulator installations
    static void install(std::ostream& os, ManipFunc func)
    {
        CustomManipHandle* handle =
            static_cast<CustomManipHandle*>(os.pword(handleIndex()));

        // check if its installed on this ostream
        if (handle == NULL)
        {
            // install it
            handle = new CustomManipHandle();
            os.pword(handleIndex()) = handle;

            // install the callback so we can destroy it
            os.register_callback (CustomManip<TYPE>::streamEvent,0);
        }

        handle->func_ = func;
    }

    static void uninstall(std::ios_base& os)
    {
        CustomManipHandle* handle =
            static_cast<CustomManipHandle*>(os.pword(handleIndex()));

        //delete the installed Custommanipulator handle
        if (handle != NULL)
        {
            os.pword(handleIndex()) = NULL;
            delete handle;
        }
    }

    static void streamEvent (std::ios::event ev,
                             std::ios_base& os,
                             int id)
    {
        switch (ev)
        {
            case os.copyfmt_event:
                std::cout << "copyfmt_event\n"; break;
                // does this mean i need to copy my manip as well??
            case os.imbue_event:
                std::cout << "imbue_event\n"; break;
            case os.erase_event:
                std::cout << "erase_event\n";
                uninstall(os);
                break;
        }
    }

    static void print(std::ostream& os, const TYPE& data)
    {
        CustomManipHandle* handle
            = static_cast<CustomManipHandle*>(os.pword(handleIndex()));

        if (handle != NULL)
        {
            handle->func_(os, data);
            return;
        }

        data.printDefault(os);
    }
};

/**********************************
 ****************TEST**************
 **********************************/

class A
{
    int v_;
public:
    A(int v) :
        v_(v)
    {}

    void printDefault(std::ostream& os) const { os << " " << v_ << " default\n"; }
    void printVer1   (std::ostream& os) const { os << " " << v_ << " abc\n"; }
    void printVer2   (std::ostream& os) const { os << " " << v_ << " xyz\n"; }
    void printParam  (std::ostream&     os,
                      const std::string paramA,
                      const int         paramB)  const
    {
        os << " " << v_ << " param: " << paramA << " " << paramB << "\n";
    }
};

std::ostream& operator<<(std::ostream& os, const A& a)
{
    CustomManip<A>::print(os, a);
    return os;
}

class B
{
public:
    void printDefault(std::ostream& os) const { os << " default\n"; }
    void printVer3   (std::ostream& os) const { os << " mno\n"; }
};

std::ostream& operator<<(std::ostream& os, const B& b)
{
    CustomManip<B>::print(os, b);
    return os;
}

std::ostream& manip_default (std::ostream& os)
{
    CustomManip<A>::uninstall(os);
    CustomManip<B>::uninstall(os);
    return os;
}

std::ostream& manip_ver1 (std::ostream& os)
{
    CustomManip<A>::install(os,
                            [](std::ostream& oos, const A& a)
                            {
                                a.printVer1(oos);
                            });
    return os;
}

std::ostream& manip_ver2(std::ostream& os)
{
    CustomManip<A>::install(os,
                            [](std::ostream& oos, const A& a)
                            {
                                a.printVer2(oos);
                            });
    return os;
}

std::ostream& manip_ver3(std::ostream& os)
{
    CustomManip<B>::install(os,
                            [](std::ostream& oos, const B& b)
                            {
                                b.printVer3(oos);
                            });
    return os;
}

CustomManip<A>::Installer manip_param(const std::string str,
                                      const int other)
{
    return CustomManip<A>::make_installer(
        [str, other](std::ostream& oos, const A& a)
        {
            a.printParam(oos, str, other);
        });
}

int main()
{
    A a(1);
    B b;

    std::cout << a
              << b
              << "\n";

    std::cout << manip_ver1 << a
              << manip_ver2 << a
              << manip_ver3 << b
              << manip_default << a
              << manip_ver1 << a
              << manip_param("testing", 4) << a
              << b
              << "\n";

    std::stringstream oss;
    oss << manip_ver1 << a
        << manip_ver2 << a
        << manip_ver3 << b
        << manip_default << a
        << manip_ver1 << a
        << manip_param("testing", 4) << a
        << b
        << "\n";

    std::cout << oss.str();
}

The resulting output looks like
 1 default
 default

new handle at 0x307de0
 1 abc
 1 xyz
new handle at 0x307e50
 mno
del handle at 0x307de0
del handle at 0x307e50
 1 default
new handle at 0x307de0
 1 abc
 1 param: testing 4
 default

new handle at 0x307ef0
new handle at 0x307e90
del handle at 0x307ef0
del handle at 0x307e90
new handle at 0x307e90
 1 abc
 1 xyz
 mno
 1 default
 1 abc
 1 param: testing 4
 default

erase_event
del handle at 0x307e90
erase_event
erase_event

No comments:

Post a Comment