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). 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 ). 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). The library is called boost.assign and can be found at
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.
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]:
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.
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,
"">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:
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 ); .
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.
General
News
Question
Answer
Joke
Rant
Admin