Sticky custom stream manipulator

后端 未结 1 1279
轻奢々
轻奢々 2020-12-14 11:16

How do I implement my own custom stream manipulator so that it is sticky. For example, I want to convert integers to binary such that:

cout << \"decima         


        
相关标签:
1条回答
  • 2020-12-14 12:15

    Doing the whole things is a bit involved. To make it comprehensible, I'll start with the basic stuff: Using custom formatting flags for user-defined types. Custom formatting of integers will follow below.

    The IOStream classes derive [indirectly] from std::ios_base which provides two stores for data: std::ios_base::iword() and std::ios_base::pword() for ints and void*, respectively. Maintaining allocated memory stored with std::ios_base::pword() is non-trivial and, fortunately, not needed for this relatively simple use-case. To use these function which both return a non-const reference to the corresponding type, you normally allocate an index using std::ios_base::xalloc() once in your program and use it whenever you need to access your custom formatting flags. When you access a value with iword() or pword() initially it will be zero initialized. To put things together, here is a small program demonstrating this:

    #include <iostream>
    
    static int const index = std::ios_base::xalloc();
    
    std::ostream& custom(std::ostream& stream) {
        stream.iword(index) = 1;
        return stream;
    }
    
    std::ostream& nocustom(std::ostream& stream) {
        stream.iword(index) = 0;
        return stream;
    }
    
    struct mytype {};
    std::ostream& operator<< (std::ostream& out, mytype const&) {
        return out << "custom-flag=" << out.iword(index);
    }
    
    int main()
    {
        std::cout << mytype() << '\n';
        std::cout << custom;
        std::cout << mytype()  << '\n';
        std::cout << nocustom;
        std::cout << mytype() << '\n';
    }
    

    Now, an int like 4 isn't a user-define type and there is already an output operator defined for these. Fortunately, you can customize the way integers get formatted using facets, more specifically using std::num_put<char>. Now, to do so you need to do a number of steps:

    1. Derive a class from std::num_put<char> and override the do_put() members you want to give specialized behavior to.
    2. Create a std::locale object using the newly create facet.
    3. std::ios_base::imbue() the stream with the new std::locale.

    To make things nicer for the user, you might want to conjure up a new std::locale with a suitable std::num_put<char> facet when the manipulator is used. However, before doing so, let's start off with creating a suitable facet:

    #include <bitset>
    #include <iostream>
    #include <limits>
    #include <locale>
    
    static int const index = std::ios_base::xalloc();
    
    class num_put
        : public std::num_put<char>
    {
    protected:
        iter_type do_put(iter_type to,
                         std::ios_base& fmt,
                         char_type fill,
                         long v) const
        {
            if (!fmt.iword(index)) {
                return std::num_put<char>::do_put(to, fmt, fill, v);
            }
            else {
                std::bitset<std::numeric_limits<long>::digits> bits(v);
                size_t i(bits.size());
                while (1u < i && !bits[i - 1]) {
                    --i;
                }
                for (; 0u < i; --i, ++to) {
                    *to = bits[i - 1]? '1': '0';
                }
                return to;
            }
        }
    #if 0
        // These might need to be added, too:
        iter_type do_put(iter_type, std::ios_base&, char_type,
                         long long) const;
        iter_type do_put(iter_type, std::ios_base&, char_type,
                         unsigned long) const;
        iter_type do_put(iter_type, std::ios_base&, char_type,
                         unsigned long long) const;
    #endif
    };
    
    std::ostream& custom(std::ostream& stream) {
        stream.iword(index) = 1;
        return stream;
    }
    
    std::ostream& nocustom(std::ostream& stream) {
        stream.iword(index) = 0;
        return stream;
    }
    
    int main()
    {
        std::locale loc(std::locale(), new num_put);
        std::cout.imbue(loc);
        std::cout << 13 << '\n';
        std::cout << custom;
        std::cout << 13  << '\n';
        std::cout << nocustom;
        std::cout << 13 << '\n';
    }
    

    What is a bit ugly is that it necessary to imbue() the custom std::locale to use the custom manipulator. To get rid of this, we can just make sure the custom facet is installed in the used std::locale and, if it is not, just install it when setting the flag:

    std::ostream& custom(std::ostream& stream) {
        if (!stream.iword(index)
            && 0 == dynamic_cast<num_put const*>(
                    &std::use_facet<std::num_put<char> >(stream.getloc()))) {
            stream.imbue(std::locale(stream.getloc(), new num_put));
        }
        stream.iword(index) = 1;
        return stream;
    }
    

    What is now left is to also override the different do_put() members to work properly with the various unsigned types and with long long but this is left as an exercise.

    0 讨论(0)
提交回复
热议问题