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 |
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
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
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).
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.
The benefits of a library solution are
The problems with a library solution are
[1] | Would anyone dare to suggest overloading on return types? :-) |