A Library Approach to Initialization

Author: T(h)orsten Ottosen
Contact: nesotto@cs.auc.dk
Organization: Department of Computer Science, Aalborg University, and Dezide Aps
Date: 29th of October 2004
Number:WG21/N1724 and J16/04-0164.
Working Group:Evolution
Copyright: T(h)orsten Ottosen 2004. All rights reserved

Table of Contents

1   Introduction

The standard committee is actively pursuing ways to enhance the initialization syntax of the language so eg. standard containers can use the same syntax as we currently use for built-in arrays. This paper briefly explains what we can currently do without any changes to the core language. The solution shown in this document is based on the authors experience from implementing boost.assign, a new library in version 1.32.0 of boost

2   The current solution

Consider the following UDT:

struct Foo
{
    Foo();               // #1
    Foo( int );          // #2
    Foo( string );       // #3
    Foo( int, string );  // #4
};

Then we can make an array of Foo as follows:

tr1::array<Foo,42> = list_of<Foo>( 5 )( "foo" )( 42, "bar" )();

This works by

  1. creating a temporary container
  2. constructing Foo objects by passing the provided arguments to the constructor
  3. adding the elements to the temporary container
  4. converting the temporary container to tr1::array<Foo,42> via a generic conversion operator.

Note that

Efficiency is probably not a concern in this context.

For maps we can do something similar:

std::map<string,int> m = 
    map_list_of ( "january",   31 )( "february", 28 )
                ( "march",     31 )( "april",    30 )
                ( "may",       31 )( "june",     30 )
                ( "july",      31 )( "august",   31 )
                ( "september", 30 )( "october",  31 )
                ( "november",  30 )( "december", 31 );

where map_list_of() is simply a short-hand for list_of<std::pair<T1,T2>>().

Users quickly wanted to insert hooks into the sequence used for initialization; for example, few people want to write the same constant N times:

const std::vector<int> v = list_of<int>( 1 )( 2 ).repeat_fun( 10, &std::rand )( 4 )( 5 ).repeat( 10, 6 );

Here repeat_fun() inserts 10 random numbers whereas repeat() inserts 10 instances of 6. Such flexibility is a major benefit of a library approach.

More complicated examples can also be constructed:

typedef vector<int>                   score_type;
typedef map<string,score_type>        team_score_map;
typedef pair<string,score_type>       score_pair;

team_score_map group = list_of< score_pair >
                             ( "Norway",  list_of(1)(0)(0) )
                             ( "USA",     list_of(0)(0)(0) )
                             ( "Andorra", list_of(0)(1)(1) );
 BOOST_ASSERT( group.size() == 3 );
 BOOST_ASSERT( group[ "Norway" ][0] == 1 );
 BOOST_ASSERT( group[ "USA" ][0] == 0 );

A more thorough tutorial can be found in the documentation of boost.assign (when version 1.32.0 of boost is released).

3   Advanced usage

The function list_of() and its siblings can also be used to create anonymous sequences. With a little more hacking we can make some quite useful and efficient abstractions possible.

Assume we have the following helper function:

template< class Range >
void print( const Range& r )
{
   std::cout << "\n";
   for( typename Range::const_iterator i = r.begin(), e = r.end();
        i !=e; ++i )
        std::cout << " " << *i;
}

An efficient way to create an anonymous sequence is possible if we ask the programmer to specify the length of the sequence:

int a=1,b=5,c=3,d=4,e=2,f=9,g=0,h=7;
print( list_of<8>(a)(b)(c)(d)(e)(f)(g)(h) );

The fact that we must specify the length to get an efficient solution is somewhat irritating.

Another interesting possibility of the library solution is that we can make sequences of references---a language solution can probably not do this. For example, this is possible by using some simple reference wrapper class:

template< class Range >
typename Range::const_iterator max_element( const Range& r )
{
    return std::max_element( r.begin(), r.end() );
}
        
int& max = *max_element( list_of<8,int&>(a)(b)(c)(d)(e)(f)(g)(h) );

In some cases we might be able to call mutating algorithms too; calling eg. std::sort() will require a standard library that guarantees to perform all swaps with a user-defined swap() (if it exists). The standard library currently does not give such guarantees; however, it is the author's opinion that such a guarantee would be useful in several other situations.

4   Conclusion

The benefits of a library solution are

The problems with a library solution are

[1]Would anyone dare to suggest overloading on return types? :-)