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 );
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 );
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 );
}
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:
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 );
assert( v.size() == 3 );
vector<float> vf = enum_n<float>( 5 );
stack<double> sd = enum_n( 4, 2.1 );
assert( sd.top() == 7.3 );
boost::array<int,3> a = enum_n( 3, -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 );
queue<complex> qc = enum_n_from( 2,
complex( 2, 0 ), complex( 1 , 2 ) );
vector<int> vi = enum_n_from( 5, 5, -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
- The boost array class does make arrays more convenient
- http://www.bdsoft.com/tools/initutil.html
- Scott. Meyers, "More Effective C++", Item 7, Addison Wesley, 1996
- K. Czarnecki and U.W. Eisenecker, "Generative programming", Addison-Wesley, 2000
- http://www.oonumerics.org/blitz/