Click here to Skip to main content
15,880,967 members
Articles / Programming Languages / C++
Article

Tiny Template Library: working with flags

Rate me:
Please Sign up or sign in to vote.
4.62/5 (10 votes)
5 Apr 20043 min read 44.2K   340   18   5
An article on how to implement and use bit flags generically and type-safely.

Introduction

Compilers MSVC v6.0/v7.0 are not supported. This project requires a compliant compiler. MSVC v7.1 or GCC v3.2.3 will work just fine.

I have another handy (I hope) addition to the TTL library. Suppose, we need to define a set of flags that can be passed around. Typically, we would use macros to define the flags and an integer data type to hold them.

An example might look like the following code:

#define WND_STYLE_CAPTION 0x01
#define WND_STYLE_BORDER 0x02

void set_style( int style );

First of all, using macros for defining constants is not always a very good idea. The reason is that macro names belong to the global namespace. For instance, you cannot do something like:

namespace x
{
    #define WND_STYLE_CAPTION 0x01
    #define WND_STYLE_BORDER 0x02
};

namespace y
{
    #define WND_STYLE_CAPTION 0x01
    #define WND_STYLE_BORDER 0x02
};

main()
{
    int fx = x::WND_STYLE_CAPTION;
    int fy = y::WND_STYLE_CAPTION;
}

To resolve the problem, we can use enum.

namespace x
{
    enum winstyle
    {
        WND_STYLE_CAPTION = 1,
        WND_STYLE_BORDER  = 1<<1
    };
};

namespace y
{
    enum winstyle
    {
        WND_STYLE_CAPTION = 1,
        WND_STYLE_BORDER  = 1<<1
    };
};

main()
{
    int fx = x::WND_STYLE_CAPTION;
    int fy = y::WND_STYLE_CAPTION;
}

An even better solution is:

namespace x
{
    struct winstyle
    {
        enum type
        {
            CAPTION = 1,
            BORDER  = 1<<1
        };
    };
};

namespace y
{
    struct winstyle
    {
        enum type
        {
            CAPTION = 1,
            BORDER  = 1<<1
        };
    };
};

main()
{
    int fx = x::winstyle::CAPTION;
    int fy = y::winstyle::BORDER|y::winstyle::CAPTION;
}

Now, everything works fine... well, almost. The problem is that we don't have type safety. The following code illustrates the problem:

//we cannot just use 'winstyle' as the 
//parameter type because it is a combination
//of winstyle values.
void set_style( int style )

main()
{
    int x = 8989;

    //'x' is just an integer and has nothing to do
    //with window styles... but the compiler
    //doesn't know about it an it is compiled just fine.
    set_style( x );
}

There is another problem that is the type of the flags holder. To hold a combination of flags, people usually use int or unsigned int. Consider a vector of flag holders:

std::vector< int > styles;

In such a case, on a 32 bit machine, we'll be allocating 3 extra bytes per vector element. So we need to watch as to what the optimal flags holder type should be. Clearly we have two objectives:

  • Make the flags type safe.
  • Make the compiler automatically select the optimal flags holder type.

Implementation

The implementation is relatively straightforward. Here are some ideas. The complete implementation can be found in TTL.

namespace impl
{
    template< 
        int Bits, 
        int type = Bits <= sizeof(char)*8?
          1:(Bits <= sizeof(short)*8?2:(Bits <= sizeof(int)*8?3:4))
      > struct bestfit;

    template< int Bits >
    struct bestfit<Bits, 1>
    {
        typedef unsigned char type;
    };

    template< int Bits >
    struct bestfit<Bits, 2>
    {
        typedef unsigned short type;
    };

    template< int Bits >
    struct bestfit<Bits, 3>
    {
        typedef unsigned int type;
    };

    template< int Bits >
    struct bestfit<Bits, 4>
    {
        typedef unsigned int type;
    };
};

template< typename T, int Bits = sizeof(int)*8, 
   typename Holder = typename impl::bestfit<Bits>::type >
struct flags
{
    typedef flags this_t;
    typedef T value_type;

    Holder f_;

    flags() : f_(0) {}
    flags( T f1 ) : f_(f1) {}
    flags( T f1, T f2 ) : f_(f1|f2) {}
    flags( T f1, T f2, T f3 ) : f_(f1|f2|f3) {}
    ...

    this_t& operator |=( const this_t& f ) { f_ |= f.f_; return *this; }
    this_t& operator &=( const this_t& f ) { f_ &= f.f_; return *this; }
    this_t operator~() { f_ = ~f_; return *this; }

    bool operator ==( const this_t& l ) const { return f_ == l.f_; }
    bool operator !=( const this_t& l ) const { return f_ != l.f_; }

    bool operator !() { return f_ == 0; }
    
    Holder get_holder() const { return f_; }
    
    bool test( const this_t& l ) const { return (f_ & l.f_)?true:false; }
    bool test() const { return f_!=0; }
};

template< typename T, int Bits, typename Holder >
flags<T, Bits, Holder> operator |(const flags<T, 
                  Bits, Holder>& l, const T& r) 
{ 
    flags<T, Bits, Holder> tmp( r );
    return l|tmp; 
}

template< typename T, int Bits, typename Holder >
flags<T, Bits, Holder> operator |(const flags<T, 
                  Bits, Holder>& l, const flags<T, 
                  Bits, Holder>& r) 
{ 
    flags<T, Bits, Holder> tmp( l );
    tmp|=r;
    return tmp; 
}

template< typename T, int Bits, typename Holder >
flags<T, Bits, Holder> operator &(const flags<T, 
                      Bits, Holder>& l, const T& r) 
{ 
    flags<T, Bits, Holder> tmp( r );
    return l&tmp; 
}

template< typename T, int Bits, typename Holder >
flags<T, Bits, Holder> operator &(const flags<T, 
                    Bits, Holder>& l, const flags<T, 
                    Bits, Holder>& r) 
{ 
    flags<T, Bits, Holder> tmp( l );
    tmp&=r;
    return tmp; 
}

As you can see, we use the bestfit class to select the optimal size holder. This class is partially specialized on sizes of char, short and int. If the number of flags is more than 8*sizeof(int), we'll get the compilation error.

Usage

Back to our example:

struct winstyle
{
    enum type
    {
        CAPTION = 1,
        BORDER  = 1<<1,

        size = 2  //the last used bit
    };
};

//use our new class template
typedef flags<winstyle, winstyle::size> winstyle_flags;

How do old flags compare to the new ones?

main()
{
    int fy = winstyle::BORDER|winstyle::CAPTION;
}

is equivalent to:

main()
{
    winstyle_flags fy(winstyle::BORDER, winstyle::CAPTION);
}

In the current implementation, you can initialize flags with up to 10 flags. If you need 11, just add a new constructor with 11 parameters and so on.

The second template parameter in flags, is looking for T::size that defines the last used bit in T. By default, the size is 8*sizeof(int) bits.

Now, our set_style() can be rewritten in a type safe fashion.

void set_style( winstyle_flags style );

The usage of flags is not really different from the traditional case. You can apply boolean and bitwise operators. Some examples are:

main()
{
    winstyle_flags fx( winstyle::CAPTION );
    winstyle_flags fy( winstyle::BORDER, winstyle::CAPTION);

    if( fx == fy )
    {
    ...
    }

    fx |= y::winstyle::BORDER;

    if( fx.test(winstyle::CAPTION) ) //is CAPTION set
    {
    ...
    }


    winstyle_flags fz = fx & fy;
}

TTL has a function for mapping one set of flags to another set. For instance, if we need to map our winstyle flags to Win32 WS_CAPTION and WS_BORDER, we can define the flag_map class that will hold our associations.

//flag_map keeps a map of pairs (my_flag, win32_flag)
struct flag_map
{
    //the first parameter is the key
    typedef std::multimap< winstyle_flags, int > map;

    //the above could also be
    //typedef std::vector< std::pair< winstyle_flags, int > > map;
    
    flag_map()
    {
        if( init_ ) //initialize only once
        {
            init_ = false;
            map_.insert( winstyle::CAPTION, WS_CAPTION );
            map_.insert( winstyle::BORDER, WS_BORDER );
        }
    }

    static bool init_;
    static map map_;
}

void set_style( winstyle_flags style );

main()
{
    set_style( winstyle_flags(winstyle::CAPTION,winstyle::BORDER)  )
}

void set_style( winstyle_flags style )
{
    flag_map map;
    //map our flags to Win32
    int w32style = ttl::flg::flag_mapper( map.map_.begin(), map.map_.end(), style );

    ::CreateWindow( ... w32style );  //use Win32 function with 'style' :)
}

flag_mapper requires that the data type de-referenced by the input iterator had the interface of std::pair.

If you do need to convert flags< > to an 'int', use get_holder().

winstyle_flags fx( winstyle::CAPTION );
int f = fx.get_holder();

Using TTL

TTL is a completely header based library. There is no need to link to any .lib files. To use TTL:

  • Copy TTL files to folder.
  • Add the folder to the list of include folders in your compiler.
  • Include TTL headers such as:
    #include "ttl/flg/flags.hpp"

    As for now, flags.hpp can be used as a standalone header separately from the rest of TTL.

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
kig
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionenum in class? Pin
wheregone5-Nov-07 22:57
wheregone5-Nov-07 22:57 
Generalttl::sig::signal Pin
WREY20-May-05 13:24
WREY20-May-05 13:24 
GeneralRe: ttl::sig::signal Pin
kig22-May-05 12:22
kig22-May-05 12:22 
Generalrobust return values Pin
Mårten R4-Aug-04 7:39
Mårten R4-Aug-04 7:39 
GeneralRe: robust return values Pin
kig4-Aug-04 10:04
kig4-Aug-04 10:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.