Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Pretty Good Initialization Library

0.00/5 (No votes)
25 Nov 2004 1  
Are you tired of filling data manually into STL containers? With the Initialization Library it gets a lot easier.

Preface

The author reminds the reader that a more in depth description of this library and its implementation appeared in the October 2003 issue of C/C++ Users Journal in the article "Non-intrusive Container Initialization". The part featured in this article has been refined and is waiting for a review at boost (see http://www.boost.org/). The other part about enumerating ranges was not featured in the article. Having a templated conversion operator can still be tricky for some compilers, but Leor Zolman has managed to port it to a wide range of compilers in his own initilization library (see http://www.bdsoft.com/tools/initutil.html ). Please note that the forwarding mechanism can be greatly simplified by calling container.insert( container.end(), new_value ) ).

September 25th: A new superior library that incorporates most of the ideas presented in this article has now been releasedas part of boost 1.32 (see www.boost.org). The library is called boost.assign and can be found at http://www.boost.org/libs/assign/doc/index.html

Contents

Introduction

Are you tired of filling data manually into STL containers? With the Initialization Library it gets a lot easier. One can simply say

vector<int>  primes;
set_cont( v ) += 1, 2, 3, 5, 7, 11;
     
list<int>  ten_odd = enum_n( 10, 2 ); 
// '{ 1,3,5,7,9,11,13,15,17,19 }'     


map<const char*,int>  months;  
set_map( months ) += "january",   31, "february", 28,
                     "march",     31, "april",    30,
                     "may",       31, "june",     30,
                     "july",      31, "august",   31,
                     "september", 30, "october",  31,
                     "november",  30, "december", 31;

class Widget;
deque<Widget> dw;
set_cont( dw ) += Widget( 2, 2 ), Widget( "name", 2 );
     

The key idea is to overload the operator,() in a non-intrusive and convenient manner. Some convenient functions can be used to create enumerated or generated data.

Motivation

The reason that this library exist can be summarized as follows:

Quick and easy initialization of STL containers.

If one needs to fill STL containers with constant data, it is a somewhat tedious work. This need might be frequent in learning, testing and prototyping situations. In other languages the job is often easy. For example, in Perl one can say

@date  = (8, 24, 70);   
%fruit = ('apples', 3, 'oranges', 6); 

to assign to an array or map, respectively. The array syntax is almost identical in C++, but since built-in arrays are not particularly handy, one will usually stuff data into an STL container [1]. In C++ one can rely on two approaches [2]:

  • use a temporary array
  • use a member function of the container

The first approach is for instance

const int N = 6;
const char* a[N] = { "isomer", "ephemeral", "prosaic", 
                    "nugatory", "artichoke", "serif" };
set<const char*>c A( a, a + N );

which requires manual book-keeping of the array size. The latter approach is even worse:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
...

Clearly one would be better off with a syntax similar to that of Perl. Moreover, it would be nice if it worked for containers of arbitrary types and not just containers of built-in types.

Synopsis

We shall first present the interface and then discuss implementations details. Template and namespace parameters on return types has been hidden to keep the synopsis easier to use; the curious can simply look in init.hpp The interface is quite small:

namespace init
{    
    
    template< typename Container >
    "#comma">Comma_initializer      "#set_cont">set_cont( Container& c );

    template< typename Container >
    "#map_comma">Map_comma_initializer  "#set_map">set_map( Container& c );

    //////////////////////////////////////////////

    // enums 

    //////////////////////////////////////////////

      
    template< typename T >
    "#any">Any_container  
    "#enum_n">enum_n( size_t n );

    template< typename T >
    Any_container  
    "#enum_n2">enum_n( size_t n, T step_size );

    template< typename T >
    Any_container  
    "#enum_n_from">enum_n_from( size_t n, T from );

    template< typename T >
    Any_container  
    "#enum_n_from2">enum_n_from( size_t n, T from, T step_size );

    template< typename T, typename Functor >
    Any_container  
    "#n_generated">n_generated_by( T n, Functor generator );

} // namespace 'init'

Comma_initializer and Map_comma_initializer are classes that wrap the normal member functions like push_back() and insert() in a call to operator,(). In this manner the interface will be the same for all containers. Overloading operator comma is sometimes viewed as a bad practice [3]. However, it has been done with success in eg. the Generative Matrix Computation Library and Blitz to initialize matrices (see [4]) and [5]). The Initialization Library overloads the comma operator in a safe manner by letting "#set_cont">set_cont and "#set_map">set_map return an object that is responsible for the initialization. Therefore it takes explicit action from the programmer to begin using the overloaded operator,().

All the enum() functions are meant for generating sequences of numeric types. This means built-in types, complex, "http://www.boost.org/libs/rational/index.html">boost::rational or types with similar behavior. The enum() functions come all come in two flavors: one that enumerate numbers with an interval of 1 and one that allows the user to specify the interval size.

Any_container is a class that converts implicitly to all the containers supported by the library. The table below also shows what member function the overloaded comma operator forwards to:

Standard container

 
vector push_back()
list push_back()
deque push_back()
set insert()
multiset insert()
map insert()
multimap insert()
stack push()
queue push()
priority_queue push()
 

SGI container extensions

slist insert()
hash_set insert()
hash_multiset insert()
hash_map insert()
hash_multimap insert()
 

Boost containers

array Not available

An explanation for each function follows.

  • Comma_initializer set_cont( Container& c );

    set_cont is short for "set container". Most of the functionality of the library is available through this function. Using it requires two steps: create the container and call the function on the container:

    vector<float> vf;
    set_cont( vf ) += 1.f, 3.f, 5.f;
    set_cont( vf )  = 1.f;

    The difference between using operator+=() and operator=() is the same as with built-in types. The former adds to the container whereas the latter clears the container before adding new elements.

    It is worth noticing that the template code allows one to initialize a container of any type provided that the type meets the common requirements of copy-construction and copy-assignable. One will simply call constructors directly and initialize the container with anonymous objects.

    One can also use containers that are not currently supported if they have a push_back() member function. The reason for this is that push_back() is the default insertion policy and specializations have been written for all other containers. Otherwise, one must make a partial specialization of the Insert_policy class for one's own container.

  • Map_comma_initializer set_map( Container& c );

    The function provides the same interface as set_cont() except it is meant for map classes. Using it is a little different since one needs to specify key-data pairs:

    map<string,int> m;
    set_map( m ) += "fish", 1, string( "horse" ), 2;
    set_map( m )  = "cow", 1;
               

    As before, operator=() resets the containers first. Again, the code will work with arbitrary types that fulfill the requirements of the container. If one makes an error like

    set_map( m ) += "fish", "fish", 2, 3;
               

    an assertion will trigger at run time because the key-data alternation was wrong.

    If one needs to use a custom map container, it will work immediately if the map class supports insert( const value_type& ) where value_type is a pair< const key,data > ). Otherwise, one needs to make a partial specialization of Map_insert_policy to support one's custom container.

  • Any_container enum_n( size_t n );
    Any_container enum_n( size_t n, T step_size );

    enum_n is short for "enumerate n elements". The first container will hold the n elements

    { 1, ..., n } 

    and the second will hold the n elements

    { 1 + 0*step_size, ..., 1 + (n-1)*step_size } .

    In the one-argument version, one must explicitly specify the type of the generated values as a template parameter.

    One can generate reverse sequences by specifying a negative step_size.

    Example:

    vector<int> vi = enum_n<int>( 3 );
    // 1,2,3
    
    assert( v.size() == 3 );
    vector<float> vf = enum_n<float>( 5 );
    
    stack<double> sd = enum_n( 4, 2.1 );
    // 1.0, 3.1, 5.2, 7.3
    
    assert( sd.top() == 7.3 );
               
    boost::array<int,3> a = enum_n( 3, -1 );
    // 1, 0, -1
  • Any_container enum_n_from( size_t n, T from );
    Any_container enum_n_from( size_t n, T from, T step_size );

    enum_n_from is short for "enumerate n elements starting from". The first container will hold the n elements

    { from + 0, ..., from + (n-1) } 

    whereas the second will hold the n elements

    { from + 0*step_size, ...,from + (n-1)*step_size }

    One does not need to specify a template parameter since the contained type will be deduced from the second parameter.

    One can generate reverse sequences by specifying a negative step_size.

    Example:

    list<int> = enum_n_from( 4, 4 );
    // 4,5,6,7
    
    
    queue<complex> qc = enum_n_from( 2, 
            complex( 2, 0 ), complex( 1 , 2 ) );
    // (2,0), (3,2), (4,4)
    
    
    vector<int> vi = enum_n_from( 5, 5, -1 );
    // 5, 4, 3, 2, 1
    
           
  • Any_container
    n_generated_by( T n, Functor generator );


    Call the generator n times and return the results in a container. The function simply wraps the std::generate algorithm. This means that

    vector<int> v = n_generated_by( 10, some_functor() );

    is equivalent with

    vector<int> v( 10 );
    generate( v.begin(), v.end(), some_functor() );

    and that the requirements for using the algorithm remain same. In particular, the generator must return type T and take no arguments. Note that the client is required to supply the contained type as the type of n. For example,

    vector<float> f = n_generated_by( 10.f, &rand ); . 

Acknowledgement

The idea for an initialization library is not new . The functionality of the library resembles Leor Zolman's STL Container Initialization Library a great deal. Leor Zolman has also contributed with helpful feedback.

Download

  • init.hpp - main header ( will change into several the near future )
  • tst.cpp - test program ( won't compile alone, download some other of Leor Zolman's utilities here )

References

  1. The boost array class does make arrays more convenient
  2. http://www.bdsoft.com/tools/initutil.html
  3. Scott. Meyers, "More Effective C++", Item 7, Addison Wesley, 1996
  4. K. Czarnecki and U.W. Eisenecker, "Generative programming", Addison-Wesley, 2000
  5. http://www.oonumerics.org/blitz/

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