Sunteți pe pagina 1din 57

The Lambda Library

Lambda Abstra tion in C++

Jaakko Jrvi
jaakko.jarvi s.utu.

Gary Powell

Sierra On-Line Ltd.


gary.powellsierra. om

Turku Centre for Computer S ien e


TUCS Te hni al Report No 378
November 2000
ISBN 952-12-0758-2
ISSN 1239-1891

Abstra t

The Lambda Library (LL) adds a form of lambda abstra tion to C++.
The LL is implemented as a template library using standard C++; thus
no language extensions or prepro essing is required. The LL onsists of a
ri h set of tools for dening unnamed fun tions. Parti ularly, these unnamed
fun tions work seamlessly with the STL algorithms. The LL oers signi ant
improvements, in terms of generality and ease of use, ompared to the binders
and fun tors in the C++ standard library.

Keywords:

lambda abstra tion, generi programming, C++ templates

The Lambda Library

Lambda Abstra tion in C++

Jaakko Jrvi
jaakko.jarvi s.utu.

Gary Powell

Sierra On-Line Ltd.


gary.powellsierra. om

Turku Centre for Computer S ien e


TUCS Te hni al Report No 378
November 2000
ISBN 952-12-0758-2
ISSN 1239-1891

Introdu tion

The ability to dene lambda fun tions, i.e. unnamed fun tions, is a standard
feature in fun tional programming languages. For some reason, this feature
does not appear in any of the mainstream pro edural or obje t-oriented languages. This arti le introdu es the Lambda Library whi h xes this 'omission'
for C++.
The Lambda Library (LL) is a C++ template library implementing a
form of lambda abstra tion for C++. The library is designed to work with
the Standard Template Library (STL) [SL94, now part of the C++ Standard
Library [C++98. The paper assumes some familiarity with the STL.
The arti le onsists of two parts. In the rst part we explain how to use
the library, in the se ond part we go over the implementation details in all
their glory.

1.1 Motivation
Typi ally STL algorithms operate on ontainer elements via fun tions and
fun tion obje ts passed as arguments to the algorithms. An obje t of a lass
with a fun tion all operator is a fun tion obje t. Fun tion obje ts are
sometimes alled fun tors as well.
The STL ontains predened fun tion obje ts for some ommon ases
(su h as plus, less and negate). In addition, it ontains adaptors for reating fun tion obje ts from fun tion pointers et . Further, it ontains two
binder templates bind1st and bind2nd. With binders, you an reate a
unary fun tion obje t from a binary fun tion obje t by binding one of the
arguments to a onstant value. Some STL implementations ontain fun tion
omposition operations as extensions to the standard [STL00.
The binder templates an be seen as kind of urrying operators, but
together with negaters and fun tion omposition operators the goal of all
these tools is lear: to make it possible to spe ify unnamed fun tions in a all
to an STL algorithm. Still in other words, the goal is to be able to pass ode
fragments as arguments to fun tions. However, the tools in the Standard
Library provide only a small step towards this goal. Unnamed fun tors
built as ompositions of standard fun tion obje ts, binders, adaptors et .
are very hard to read in all but the simplest ases. Moreover, the 'lambda
abstra tion' with the standard tools is full of restri tions. For example, the
standard binders allow only one argument of a binary fun tion to be bound;
there are no binders for 3-ary, 4-ary et . fun tions. See [Jr00 for a more
in-depth dis ussion about these restri tions.
The Lambda Library solves these problems. The syntax is intuitive and
there are no (well, fewer) arbitrary restri tions. The on rete onsequen es
of the LL on the tools in the Standard Library are:
1

 The standard fun tors plus, minus, less et . be ome unne essary.
Instead, the orresponding operators are used dire tly.
 The binders bind1st and bind2nd are repla ed by a more general
bind fun tion template. Using bind, arbitrary arguments of pra ti ally
any C++ fun tion an be bound. Furthermore, bind makes ptr_fun,
mem_fun and mem_fun_ref adaptors unne essary.
 No expli it fun tion omposition operators are needed.
We'll take an example to demonstrate what LL's impa t an be on ode
using STL algorithms. The following example is an extra t from the do umentation of one STL implementation:

... al ulates the negative of the sines of the elements in a ve tor, where
the elements are angles measured in degrees. Sin e the C library fun tion
sin takes its arguments in radians, this operation is the omposition of three
operations: negation, sin, and the onversion of degrees to radians.
ve tor<double> angles;
ve tor<double> sines;
onst double pi = 3.14159265358979323846;
...
assert(sines.size() >= angles.size());
transform(angles.begin(), angles.end(), sines.begin(),
ompose1(negate<double>(),
ompose1(ptr_fun(sin),
bind2nd(multiplies<double>(), pi / 180.0))));
Using LL onstru ts, the transform fun tion all an be written as:

transform(angles.begin(), angles.end(), sines.begin(),


- bind(sin, free1 * pi / 180.0));
The operator- repla es the all to negate, bind repla es ompose1, ptr_fun
be omes unne essary, along with bind2nd, and the all to multiplies is
repla ed with the operator*. The argument free1 is a pla eholder representing the parameter of the fun tion obje t. At ea h iteration, it will be
substituted for the element from the ontainer of angles. Noti e how the
ode using the Lambda Library is mu h easier to read.

1.2 About expression templates


The LL implementation is based on expression templates. The basi idea
behind expression templates is to overload operators to reate expression
obje ts to represent the expression and its arguments instead of evaluating
the operator instantly. As a result, the type of an expression obje t an
2

des ribe a parse tree of the underlying expression. This expression obje t
an be manipulated in various ways (also at ompile time) prior to a tually
evaluating it, for example to yield better performan e. In the LL ase,
this manipulation means passing the expression obje t as a parameter to a
fun tion, and substituting the a tual arguments for the pla eholder obje ts.
There are s ar ely any introdu tory arti les on expression templates.
The original arti le by Veldhuizen [Vel95 des ribed expression templates
tailored for ve tor and matrix expressions. Su h expression templates are
dis ussed also in [CE00, Chapter 10 as well as in [HCKS99. The arti le on
the Expression Template Library (ET) [PH00 explains expression templates
whi h are loser to the LL implementation. We will spend more time on LL
expression templates in the se ond part of this arti le.
Expression templates are a omplex topi , so don't be dis ouraged if you
don't fully understand them. Just like the I/O streams library you an use
them anyway. In part I we are going to try and show you what you an do
with them, not the internals of how they work.

1.3 Terminology
The expression - bind(sin, free1 * pi / 180.0) shown in se tion 1.1 is
an expression template. This is a ording to ommonly established C++
terminology. However, to emphasize the analogy to fun tional programming
we all this a lambda expression, as it in ee t denes an unnamed fun tion,
or more pre isely, an unnamed fun tion obje t. We use the term lambda
fun tor to spe i ally refer to the fun tion obje t resulting from a lambda
expression. To relate our terminology with existing on epts, as we understand them, the term expression template overs the whole ma hinery for
reating expression obje ts and alling them. We need a ner segregation
between on epts; hen e we make a distin tion between lambda expressions
and lambda fun tors.

Part I

Using the Lambda Library


2

Basi usage

This se tion des ribes the basi rules for writing lambda expressions onsisting of operator invo ations and dierent types of fun tions and fun tion-like
onstru ts. Note that there are ex eptions to these basi rules. Some operators in C++ have spe ial semanti s, whi h is ree ted in the semanti s of
the orresponding lambda expressions as well. We over these spe ial ases
in se tion 3.
3

2.1 Introdu tory examples


It is easiest to understand how to use the LL by looking at some examples.
So let's start with some simple expressions and work up. First, we'll initialize
the elements of a ontainer (e.g. a list) to the value 1:

list<int> v(10);
for_ea h(v.begin(), v.end(), free1 = 1);
In this example free1 = 1 reates a lambda fun tion whi h assigns the value
1 to every element in v; remember that free1 is a pla eholder, an empty
slot, whi h will be lled with a value at ea h iteration. Next, we reate
a ontainer of pointers and make them point to the elements in the rst
ontainer v:

list<int*> vp(10); // a ontainer of pointers


transform(v.begin(), v.end(), vp.begin(), &free1);
Here we take the address of ea h element in v (with &free1) and assign it to
the orresponding element in vp. Now lets hange the values in v. For ea h
element we all some fun tion foo passing the original value of the element
as an argument to foo:

int foo(int);
for_ea h(v.begin(), v.end(), free1 = bind(foo, free1));
Next we'll sort the elements of vp:

sort(vp.begin(), vp.end(), *free1 > *free2);


In this all to sort, we are sorting the elements by their ontents in des ending order. Note that the lambda expression *free1 > *free2 ontains two
dierent pla eholders, free1 and free2. Consequently, it reates a binary
lambda fun tor. When this fun tor is alled, the rst argument is substituted for free1 and the se ond argument for free2. Finally we'll output
the sorted ontent of vp separated by line breaks:

for_ea h(vp.begin(), vp.end(), out << *free1 << endl);


After seeing these examples it should be obvious how to write the lambda
expression to, say, in rement ea h element in v by 6 times the result of the
all to a zero-argument fun tion named rand:

for_ea h(v.begin(), v.end(), free1 += 6 * bind(rand));


See this is easy. We must admit that there are some traps and we'll try and
point them out as we go along. But in general you will have no trouble using
this new library.
4

2.2 Pla eholders


In lambda fun tions o urring in lambda al ulus and in fun tional programming languages the formal parameters are ommonly named within
the lambda expression with some expli it syntax.For example, the following
denition gives the names x and y to the formal parameters of an addition
fun tion:

xy:x + y
The C++ ounterpart of the above expression is written as:

free1 + free2
In the C++ version the formal parameters, i.e. the pla eholder variables,
have predened names. There is no expli it synta ti onstru t for C++
lambda expressions; the use of a pla eholder variable in an expression impli itly turns the expression into a lambda expression.1
So far we have used the pla eholders free1 and free2. The LL supports
one more: free3. This means that lambda fun tors an take one, two or
three arguments passed in by the STL algorithm; zero parameters is possible
too. No STL algorithm a epts a fun tor with the number of arguments
greater than two, so the three pla eholders should be enough. In fa t, the
third pla eholder is a ne essity in order to implement all the features of the
urrent library (see se tion 3.8, whi h introdu es freeE, another in arnation
of free3).
The pla eholders may seem somehow magi al, but they are just normal
variables. The variables themselves are not important but rather their types
are. The types serve as tags, whi h allow us to spot the pla eholders from
the arguments stored in a lambda fun tor, and later repla e them with the
a tual arguments that are passed in the all to the lambda fun tor. The
LL provides typedefs for the pla eholder types, so it is easy to dene the
pla eholder names to your own liking. For example, to use arg1, arg2 and
arg3 instead of free1, free2 and free3, you an write:

free1_type arg1;
free2_type arg2;
free3_type arg3;
Veldhuizen [Vel95 was the rst to introdu e the on ept of using pla eholders in expression templates. The LL pla eholders are somewhat dierent.
You may have noti ed from the previous examples that we do not spe ify any
types for the arguments that the pla eholders stand for. A pla eholder leaves
the argument totally open, in luding the type. This means that the lambda
1

This doesn't hold for all expressions, whi h we will explain in the sequel.

fun tor an be alled with arguments of any type for whi h the underlying
fun tion makes sense.
Consider again the rst example we showed in se tion 2:

for_ea h(v.begin(), v.end(), free1 = 1);


The lambda expression free1 = 1 reates a unary lambda fun tor, whi h
an be alled with any obje t x, for whi h x = 1 is a valid expression. The
for_ea h algorithm iterates over a ontainer of integers, thus the lambda
fun tor is alled with an argument of type int.
Sin e the type of the pla eholder argument is left open, it is obvious that
the return type of the lambda fun tor is not known either. Not to worry,
the LL has a type dedu tion system that gures out the return type when
the lambda fun tor is alled. It overs operators of built-in types and 'wellbehaved' operators of user dened types; and for your user dened operators
with unorthodox return types, the dedu tion system is easy to extend. We
will over this aspe t of the LL in the latter part of the paper.

2.3 Fun tions as lambda expressions


Obviously, the use of a pla eholder in a fun tion all does not turn the
invo ation into a lambda expression. As the examples above show, the bind
fun tion template serves for this purpose. The syntax of a lambda expression
reated with the bind fun tion is:

bind(target-fun tion, bind-argument-list)


We use the term bind expression to refer to this type of lambda expressions.
In a bind expression, the bind-argument-list must be a valid argument
list for target-fun tion, ex ept that any argument an be repla ed with
a pla eholder, or more generally, with another lambda expression. Where a
pla eholder is used in pla e of an a tual argument, we say that the argument
is unbound or free.
The target fun tion an be a pointer to fun tion, a referen e to fun tion
or a fun tion obje t. Moreover, it an be a pointer to a member fun tion or
even a pla eholder, or again more generally, a lambda expression. Note that
we use the term target fun tion with other types of lambda expressions as
well, always to denote the underlying operation of the lambda expression.
2.3.1

Fun tion pointers as targets

The target fun tion an be a pointer or a referen e to a non-member fun tion


and it an be either bound or unbound. For example, suppose A, B, C and X
are some types:
6

X foo(A, B, C); A a; B b; C ;
...
bind(foo, free1, free2, )
bind(&foo, free1, free2, )
bind(foo, free1, free1, free1)
bind(free1, a, b, )
The rst bind expression returns a binary lambda fun tor. The se ond bind
expression has an equivalent fun tionality, it just uses a fun tion pointer
instead of a referen e. The third bind expression demonstrates that a ertain pla eholder an be used multiple times in a lambda expression. The
argument will be dupli ated in ea h pla e that the pla eholder is used. For
this bind expression to make sense, and to ompile, the argument to the
resulting unary lambda fun tor must be impli itly onvertible to A, B and C.
The fourth bind expression shows that even the target fun tion an be left
unbound; the resulting lambda fun tor takes one parameter, the fun tion to
be alled with the arguments a, b and .
In C++, it is possible to take the address of an overloaded fun tion only
if the address is assigned to or used to initialize a properly typed variable.
This means that overloaded fun tions annot be used in bind expressions
dire tly:

void foo(int); void foo(float); int i;


...
bind(&foo, free1)(i); // error
...
void (*pf1)(int) = &foo;
bind(pf1, free1)(i); // ok
Also, there is no way to gather the default arguments to a fun tion:

void goo(int, int = 10);


bind(goo, free1)(i); // error: goo takes two arguments
2.3.2

Fun tion ob je ts as targets

Fun tion obje ts an also be used as target fun tions. The return type
dedu tion system requires that the fun tion obje t lass denes the return
type of the fun tion all operator as the typedef result_type. This is the
onvention used with the fun tion obje ts in the Standard Library. For
example:

stru t A {
typedef B result_type;
B operator()(X, Y, Z);
};
7

The above fun tion obje t an be used like this:

A a; X x; Y y; Z z; list<A> la; list<Z> lz;


...
for_ea h(lz.begin(), lz.end(), bind(a, x, y, free1));
for_ea h(la.begin(), la.end(), bind(free1, x, y, z));
The fun tion all operator an be overloaded within a lass. However, the
return type dedu tion system an handle only one return type per fun tion
obje t lass. Consequently, all the overloaded fun tion all operators within
one lass must have the same return type, if you want to be able to use them
as target fun tions (there is a way around this, see se tion 6.7.1).
2.3.3

Member fun tions as targets

The form of the bind expression with member fun tion targets is slightly
dierent. By onvention, we have hosen to de lare the bind fun tions with
the following format:

bind(target-member-fun tion, target-obje t, bind-argument-list)


If the rst argument is a pointer to a member fun tion of some lass A, the
se ond argument is the target obje t, that is, an obje t of type A for whi h
the member fun tion is to be alled. A tually, a bound obje t argument an
be either a referen e or pointer to the obje t; the LL supports both ases
with the same interfa e:

bool A::foo(int); A a; A* ap; ve tor<int> ints;


...

// referen e is ok:

find_if(ints.begin(), ints.end(), bind(&A::foo, a, free1));

// pointer is ok:

find_if(ints.begin(), ints.end(), bind(&A::foo, ap, free1));


The fun tionality is identi al in both ases. Similarly, if the obje t argument
is free, the resulting fun tor an be alled both via a pointer or a referen e:

list<A> refs; list<A*> ptrs;


find_if(refs.befin(), refs.end(), bind(&A::foo, free1, 1));
find_if(ptrs.begin(), ptrs.end(), bind(&A::foo, free1, 1));
Similar to other types of bind expressions, the target-member-fun tion an
be left open as well.
8

2.4 Operators as lambda expressions


We have overloaded almost every operator for lambda expressions. Hen e,
the basi rule is that any operand of any operator an be repla ed with a
pla eholder, or with a lambda expression. All the pre eding ode examples
follow this rule. However, there are some spe ial ases and restri tions:
 The return types annot be hosen freely while overloading operators
->, new, delete, new[ and delete[. Consequently, we an't overload
them dire tly for lambda expressions.
 It is not possible to overload the ., .*, and ?: operators in C++.
 The assignment and subs ript operators must be dened as member
fun tions, whi h reates some asymmetry to lambda expressions. For
example:

int i;
free1 = 1; // a valid lambda expression
i = free1; // error, no assignment from free1_type to int
A workaround for this situation is explained in se tion 2.5.
 As stated in se tion 2.2, the return type dedu tion system may not
handle all user-dened operators. In su h ases the dedu tion system
an either be extended, or temporarily overridden by expli it type information (see se tion 6.7).

2.5 Delayed onstants and variables


Lambda fun tors are built with fun tion alling rules: arguments that an be
evaluated before the lambda fun tor reation fun tion is alled, will be. This
may not be what we want. We ommonly need to prevent the evaluation of
a variable, or sometimes even a onstant, and reate a lambda fun tor out
of the variable, or onstant, instead. We all su h lambda fun tors delayed
variables, or delayed onstants, respe tively. For example, the following line
outputs a spa e followed by the elements of the ontainer a:

for_ea h(a.begin(), a.end(), out << " " << free1);


To output a spa e separated list of elements, we need to turn the onstant
" " into a lambda expression with the onstant fun tion:

for_ea h(a.begin(), a.end(), out << onstant(" ") << free1);


Now rather than writing to the stream immediately, the operator<< all
with out and a lambda fun tor builds another lambda fun tor. This lambda
fun tor will be evaluated later at ea h iteration and we get the desired result.
9

A delayed variable is simply a lambda fun tor ontaining a referen e to


a regular C++ variable and is reated with the fun tion template var. A
all var(i) turns some variable i into a lambda expression. A somewhat
arti ial, but hopefully illustrative example is to ompute the number of
elements in a ontainer using the for_ea h algorithm:

int ount = 0;
for_ea h(a.begin(), a.end(), var( ount)++);
The variable ount is delayed; it is evaluated at ea h iteration within the
body of the for_ea h fun tion.
A delayed variable, or a onstant, an be reated outside the lambda
expression as well. The template lasses var_type and onstant_type serve
for this purpose. Using var_type the previous example be omes:

int ount = 0;
var_type<int>::type v ount(var( ount));
for_ea h(a.begin(), a.end(), v ount++);
This feature is useful if the same variable appears repeatedly in a lambda
expression. This is relatively ommon with some of the ontrol lambda expressions, see se tion 3.5. In general, delayed variables are in frequent use
within ontrol lambda expressions.
Above, in se tion 2.4 we brought up the asymmetry within lambda assignment and subs ript operators. As be omes lear from the above examples,
delaying the evaluation of a variable with var is a solution to this problem:

int i;
i = free1; // error, no assignment from free1_type to int
var(i) = free1; // ok

2.6 Controlling how bound arguments are stored


We have stated that bound arguments are stored in the lambda fun tor.
However, there are a few alternative ways of doing it. Basi ally the lambda
fun tors an store temporary opies of the arguments, or hold onst or non onst referen es to them. The default is temporary opies. This means that
the value of a bound argument is xed when the lambda fun tor is reated
and remains onstant during its lifetime. For example, the result of the
lambda fun tor invo ation below is 11, not 20:

int i = 1; (free1 + i)(i = 10);


As said, this is the default, and for some expressions it makes more sense
to store the arguments as referen es. As an example, onsider the lambda
10

expression i += free1. The obvious intention is that alls to the lambda


fun tor ae t the value of the variable i, rather than some temporary opy of
it. The LL has this behavior: the left argument of the ombined assignment
operators (+=, *=, et .) are stored as referen es to non- onst. Further, to
make the streaming operators (<< and >>) intuitive, the stream argument is
stored as a referen e. Also, as array types annot be opied, lambda fun tors
store referen es to arguments that are of array types.
In lambda fun tors reated with bind expressions, the default is again
to store temporary opies. This is a reasonable default, as it prevents sideee ts via bound arguments. However, we may have target fun tions where
the side-ee ts are deliberate. Further, we may have argument types that
annot be opied, or are very expensive to opy. For these reasons the LL
provides two simple fun tions for overriding the default storing me hanism:
wrapping a bound argument with the ref fun tion states that the lambda
fun tor should store a non- onst referen e to the argument, and respe tively,
the ref fun tion states that a onst referen e should be used. For example:

A a; int i; // suppose A is a lass that an't be opied


void foo( onst A& a);
void rotate(int& j) { j < 10 ? ++j : j = 0; }
...
bind(foo, ref(a)) // annot opy a, store a onst referen e instead
bind(rotate, ref(i)) // store a referen e to i
The implementation of ref and ref is not dis ussed in part II, but rather
we des ribe the basi idea here briey (for more details, see [Jr00). These
fun tions reate wrapper obje ts that hold a referen e to the wrapped argument. The fun tions and operators that reate lambda fun tors re ognize
su h wrapper types and sele t the storing method appropriately. Further,
the wrapper obje t has a onversion operator to the wrapped referen e type,
hen e the obje t an be used transparently in pla e of the wrapped argument. Finally, note that ref and ref do not reate lambda fun tors, and
are thus dierent from var and onstant explained in se tion 2.5.

Advan ed te hniques (templates gone wild)

Our goal has been to make the LL as omplete as possible in the sense
that any C++ expression ould be turned into a lambda expression. This
se tion des ribes how to use some of the C++ spe i operators in lambda
expressions, how to write ontrol stru tures as lambda expressions and how
to onstru t and destru t obje ts in lambda expressions; we even show how
to do ex eption handling in lambda expressions. We believe that in addition
to the 'typeless' pla eholders and the unique return type dedu tion system,
these extra features really set the LL apart from its prede essors.
11

3.1 Comma operator


The LL overloads the omma operator for sequen ing lambda expressions
together, as did the ET library [PH00. The hara ter ";" is reserved by the
C++ language to mean 'end of statement'. For this library we would like
to have it mean, 'end of this lambda expression'. Unfortunately it just isn't
possible, so we are left with operator,.
Sin e omma is also the separator between fun tion arguments, extra
parenthesis are sometimes ne essary to write synta ti ally orre t lambda
expressions:

for_ea h(a.begin(), a.end(),


( out << free1 << endl, log << free1 << endl));
Here the parenthesis are used to group the two lambda expressions into one
expression, as opposed to trying to all the for_ea h fun tion with four
arguments.
The LL follows the C++ rule for always evaluating the left-hand operand
of a omma expression before the right-hand operand. In the above example,
this means that ea h element of a is guaranteed to be rst written to out
and then to log.
Note that the short ir uiting rules for the operators &&, and || are
respe ted as well. For example, the following ode sets all negative elements
of some ontainer a to zero:

for_ea h(a.begin(), a.end(), free1 < 0 && free1 = 0);


See [Mey96, pp. 3538 for a full dis ussion about the pitfalls when overloading omma and the short ir uited logi al operators.

3.2 Pointer to member operator


The operator->* is another spe ial ase. It an refer to either a member
obje t, or a member fun tion. In the ase of a member fun tion, this overloaded operator has dupli ate fun tionality to using bind. Nevertheless, it
may make the ode easier to read. For example:

stru t data {
int x;
int foo(int y);
};
ve tor<data*> v;

// ount all elements, where the member x equals to zero:

ount_if(v.begin(), v.end(), free1->*(&data::x) == 0);


12

// stream out the results of alls to data::foo(5):

for_ea h(v.begin(), v.end(),


out << (free1->*(&data::foo))(5));

// the same example using bind:

for_ea h(v.begin(), v.end(),


out << bind(&data::foo, free1, 5));

3.3 Fun tion all operator


We have also overloaded operator() for the pla eholders. We provide the
zero through nine argument ases of operator()(arg1, ..., arg9). The
same fun tionality an be a hieved using bind. The operator() has been
overloaded be ause it may make your ode easier to read and understand,
whi h is what the LL is all about. Here is an example using operator():

stru t data {

// typedef required for return type dedu tion

typedef int result_type;


int operator()(int y);
};
int y;
ve tor<data> v;
...
ount_if(v.begin(), v.end(), free1(y) == 0);
We have not overloaded operator() for arbitrary lambda fun tors, sin e this
would oni t with the operator() already dened in the lambda fun tor.

3.4 Conditional expression


Sin e it is not possible to overload the ?: operator, we have reated a fun tion alled if_then_else_return, whi h dupli ates its fun tionality. For
example:2

// if bit 0x001 is set, set bit 0x100, else set bit 0x010

for_ea h(a.begin(), a.end(),


free1 |=
if_then_else_return(
(free1 & 0x001) == 0x001, 0x100, 0x010));
The result type rules for the ?: operator are omplex but we believe we have
them en oded to the extent that it is possible.
2

Sin e we have overloaded operator|=, the for_ea h loop is as e ient as a hand


oded for loop. Unlike using transform whi h only uses operator=, the LL an use the
+=, -= et . operators whi h take advantage of not having to reate a new obje t on the
sta k and then all the assignment operator.

13

3.5 Control lambda expressions


The idea of lambda expressions for ontrol stru tures originates from the
ET Library [PH00. The LL implements the ontrol lambda expressions
of the ET library and adds more. In addition to if_then, if_then_else,
for_loop, while_loop, and do_while_loop, the LL also has swit h and
do_on e.
Control lambda expressions are fun tion templates that reate lambda
fun tors that implement the behavior of some ontrol stru ture. The arguments to these fun tion templates are lambda fun tors. For example, the
following ode outputs all even elements of some ontainer a:

for_ea h(a.begin(), a.end(),


if_then(free1 % 2 == 0, out << free1));
As an example of a loop ontrol lambda expression, the pseudo ode denition of for_loop is:

for_loop(initialization, test, in rement, body)


Again, the arguments to the for_loop fun tion are lambda fun tors. Let's
take a on rete example. The following ode adds 6 to ea h element of a
two-dimensional array:

int a[5[10; int i;


for_ea h(a, a+5,
for_loop(var(i) = 0, var(i) < 10, ++var(i),
free1[var(i) += 6));
Note the use of delayed variables to turn the for_loop arguments into
lambda expressions.
As stated in se tion 2.5, we an avoid the repeated wrapping of a variable
with var, if we reate the delayed variable beforehand using the var_type
template. Using var_type the above example be omes:

int i;
var_type<int>::type vi(var(i));
for_ea h(a, a+5,
for_loop(vi = 0, vi < 10, ++vi, free1[vi += 6));
Other loop stru tures are analogous to for_loop. The return type of all
ontrol lambda fun tors is void.
14

3.6 Swit h and do_on e


The lambda expressions for swit h ontrol stru tures, as well as for do_on e,
are more omplex sin e the number of ases may vary. The general form of
a swit h lambda expression is:

swit h_statement( ondition ,


ase_statement<label >(lambda expression ),
ase_statement<label >(lambda expression ),
...
default_statement(lambda expression )
)
The ondition is a lambda expression reating a lambda fun tor with an
integral return type. The dierent ases are reated with ase_statement
fun tions, and the optional default ase with the default_statement fun tion. The ase labels are given as expli itly spe ied template arguments to
ase_statement fun tions. The break statements are impli itly part of ea h
ase: for example, ase_statement<1>(lambda_fun tor<A>& a) generates
the ode:

ase 1:

evaluate lambda fun tor a ;


break;

We have spe ialized the swit h_statement fun tion for up to 9 ase statements.
In ase you do not re ognize the do_on e onstru t, it is a spe ialization
of the ode:

do {

ode ; if ( ondition ) break;


ode ; if ( ondition ) break;

...

ode ;
} while(false);
The lambda expression equivalent to this is written as:

do_on e (
do_statement(lambda expression , break_if( ondition )),
do_statement(lambda expression , break_if( ondition )),
... ,
)

lambda expression

15

The do_on e onstru t is similar in stru ture to the swit h lambda expression: do_statement fun tions group the ode into blo ks, just as the
ase_statement fun tions in the swit h stru ture. The last argument is the
end of the do on e stru ture, and thus a plain lambda fun tor; there is no
need for another test/break blo k.

3.7 Constru tors and destru tors as lambda expressions


Operators new and delete an be overloaded, but their return types are
xed. Parti ularly, the return types annot be lambda fun tors. As in the
ase of the onditional operator, we have dened fun tion templates to ir umvent this restri tion. The fun tion template new_ptr reates a lambda
fun tor that wraps a new invo ation, delete_ptr respe tively a lambda fun tor that wraps a deletion. For example:

int* a[10;
for_ea h(a, a+10, free1 = new_ptr<int>());
for_ea h(a, a+10, free1 = delete_ptr(free1));
The new_ptr fun tion takes the type of the obje t to be onstru ted as an
expli itly spe ied template argument. Note that new_ptr an take arguments as well. They are passed dire tly to the onstru tor invo ation and
thus allow alls to onstru tors whi h take arguments. The lambda fun tor
reated with delete_ptr rst evaluates its argument (whi h is a lambda
fun tor as well) and then alls delete on the result of this evaluation. We
have also dened new_array and delete_array for new[ and delete[.
To be able to write onstru tors as lambda expressions, we have to resort
to a set of fun tion templates again. We annot use bind, sin e it is not
possible to take the address of a onstru tor. Instead, we have dened a
set of onstru tor fun tions whi h reate lambda fun tors for onstru ting
obje ts. The lambda expression

onstru tor<type >(args )


reates a lambda fun tion whi h wraps the onstru tor all type (args ) and
returns the resulting obje t. The omplementary fun tion destru tor exists
as well. The following example reads integers from two ontainers, onstru ts
pairs out of them and inserts them into a third ontainer:

ve tor<pair<int, int> > v;


transform(x.begin(), x.end(), y.begin(), ba k_inserter(v),
onstru t<pair<int, int> >(free1, free2));

3.8 Ex eption handling in lambda expressions


The LL allows you to reate lambda fun tors that throw and at h ex eptions. At rst glan e, this may seem a straightforward addition to the library
16

 after all, if you an implement swit h statements with an arbitrary number of ase blo ks, why not try and at h blo ks? Trust us, it is not that
easy. We will explain more in the se ond part of the arti le (see se tion 9).
Nevertheless, the usage is fairly easy. The form of a lambda expression for
try at h blo ks is as follows:

try_ at h(

lambda expression ,
at h_ex eption<type >(lambda expression ),
at h_ex eption<type >(lambda expression ),

...
at h_all(lambda expression )

The rst lambda expression is the try blo k. Ea h at h_ex eption denes
a at h blo k; the type of the ex eption to at h is spe ied with the expli it
template argument. The resulting lambda fun tors at h the ex eptions as
referen es. The lambda expression within the at h_ex eption denes the
a tions to take if the ex eption is aught.
The last at h blo k an be either a all to at h_ex eption<type >, or
to at h_all. Sin e it is not possible to write at h_ex eption<...>, we
have used the fun tion at h_all to mean at h(...).
Lambda fun tors for throwing ex eptions are reated with the unary fun tion throw_ex eption. The argument to this fun tion is the ex eption to
be thrown, or a lambda fun tor whi h reates the ex eption to be thrown. A
lambda fun tor for rethrowing ex eptions is reated with the nullary rethrow
fun tion.
The gure 1. demonstrates the use of the LL ex eption handling tools.
The rst at h blo k is for handling ex eptions of type foo_ex eption. Note
the use of the free1 pla eholder in the lambda expression that denes the
body of the handler.
The se ond handler at hes ex eptions from the standard library, writes
an informative message to out and onstru ts and throws another type of
ex eption (bar_ex eption). The std::ex eption arries a string explaining
the ause of the ex eption. The explanation an be queried with the zeroargument what member fun tion; bind(&std::ex eption::what, freeE)
reates a lambda fun tor for alling the what fun tion. Note the use of
freeE as the argument. It is a spe ial pla eholder, whi h refers to the aught
ex eption obje t within the handler body. freeE is not a fulledged pla eholder, but rather a spe ial ase of free3. As a onsequen e, freeE annot
be used outside of an ex eption handler lambda expression, and free3 annot be used inside of an ex eption handler lambda expression.3 As free1
and free2 an be used inside a handler, in addition to freeE, we do not see
3

Fair enough.

17

for_ea h(
a.begin(), a.end(),
try_ at h(
bind(foo, free1), //foo may throw
at h_ex eption<foo_ex eption>(
out << onstant("Caught foo_ex eption; foo argument = ")
<< free1
),
at h_ex eption<std::ex eption>(
out << onstant("Caught std::ex eption: ")
<< bind(&std::ex eption::what, freeE),
throw_ex eption( onstru tor<bar_ex eption>(free1))
),
at h_all(
out << onstant("Unre ognized ex eption"),
rethrow()
)
)
);

Figure 1: Throwing and handling ex eptions.

18

the immediate threat of this being a true restri tion in pra ti e. In any ase,
illegal use of pla eholders is aught by the ompiler.
The last handler ( at h_all) demonstrates rethrowing ex eptions.
3.8.1

Why do ex eption handling at all?

Lambda expressions for ex eption handling were one of the last things we
added to the Lambda Library, be ause we thought it was not that important,
and be ause it was not that easy. Looking ba k, it was worth the eort.
Ex eption handling made the promise of giving you another han e; x
things up and maybe try again. But the standard fun tors with the standard
algorithms don't give that han e. If the fun tor passed to an algorithm
throws, you an't restart the algorithm where it was when the ex eption was
thrown. In other words, if there is a possibility that the fun tor throws, you
an't make ex eption safe alls to STL algorithms  unless of ourse you
rewrite the fun tor to handle the ex eptions. The LL gives you an easy way
to write ex eption safe ode. Pla ing a try/ at h blo k inside the algorithm
allows you to de ide whether to ontinue or bail.

3.9 Nesting STL algorithms with lambda expressions


At the time of writing this, we are extending the library to allow nesting
of STL algorithms. There are some issues whi h makes this ompli ated,
and we haven't armatively de ided on all the details yet. Consequently,
this se tion des ribes work in progress and the exa t syntax for nesting STL
algorithms may hange in the future versions of the library.
In se tion 3.5 we showed an example using for_loop as the fun tion
obje t passed to the for_ea h algorithm. We'll repeat the example here:

int a[5[10; int i;


for_ea h(a, a+5,
for_loop(var(i) = 0, var(i) < 10, ++var(i),
free1[var(i) += 6));
We ould do better: the inner loop should really be another for_ea h invo ation. However, for_ea h is a fun tion template, and thus annot be
passed as a parameter. To ir umvent this, we have opied the interfa e
of all the STL algorithms into a subnamespa e LL, where the names of the
standard algorithms refer to our own lambda fun tors, whi h implement the
fun tionality of the orresponding algorithms (by alling the standard fun tion template). Using a nested for_ea h, the above example be omes:

for_ea h(a, a+5, LL::for_ea h(free1, free1 + 10, free1 += 6));


Noti e the reuse of free1. In the rst two arguments of the inner for_ea h
it refers to the elements over whi h the outer for_ea h iterates. In the third
19

argument, the same pla eholder refers to the elements in the inner for_ea h.
What is going on? Well, free1 is just a pla eholder so we an reuse it wherever a pla eholder makes sense. The rst two arguments to LL::for_ea h
are just plain old lambda fun tors, whi h the LL::for_ea h fun tor evaluates at ea h iteration and passes the results forward to std::for_ea h.
The third argument is a lambda fun tor as well, but it is not evaluated, but
rather passed on to std::for_ea h as su h.

Getting, installing and using the library

To get the library, visit the LL web-site http://lambda. s.utu.fi. There


is no installation pro edure. To start using the library, you just in lude
some les. In order to keep the ore of the library lean, and to allow users
to sele tively take on the features of the LL, the basi in lude le ll.hpp
just in ludes the lambda fun tors, bind fun tions and the basi operators.
Control onstru ts and other extras an be in luded sele tively. The le
ll_all.hpp has everything in it for users who want it all.
The library is urrently in the namespa e "boost". There are a ouple
of denes to help manage the namespa e issues, one whi h allows you to
hange the LL namespa e, another to remove namespa ing all together.

Part II

Implementing the Lambda Library


5

General library design

The LL onsists of a number of layers. Ea h layer implements a ertain task


orthogonal to the tasks of the other layers. This orthogonal layering is the
key to manage the variation between dierent types of lambda expressions.
Within ea h layer, it is enough to write template spe ializations with respe t
to a single varying fa tor.
Even with the stri t layering, the ar hite ture of the LL is omplex. We
will not try to des ribe the library ode entirely, but rather will look at
the hain of template instantiations taking pla e while ompiling a simple
lambda expression. In this way, the purpose of ea h layer be omes lear.
The presented ode is somewhat simplied for larity. Parti ularly, while
the arguments of the lambda expressions are always stored inside a tuple
obje t (see se tion 5.2) in the lambda fun tor, the details vary. Depending
on the lambda expression and the type of the arguments, the arguments
are stored either as onst or non- onst, and either as opies or referen es.
This variability is a hieved with a set of traits lasses whi h perform the
ne essary type onversions. Therefore, in the library ode ea h argument
20

type is wrapped inside one of these traits lass instantiations. We do not


show these wrappings here. Moreover, we ommonly use the make_tuple
fun tions (see se tion 5.2) to reate the argument tuples, while in the library
ode we are alling the tuple onstru tors dire tly and spe ify the full type
of the tuples. Although these details are ru ial for a fully fun tional library,
we believe that omitting them here is bene ial: the ode is mu h easier to
read and the general library design does not get lost as easily.
To start with, the entral template in the implementation is the template
lass lambda_fun tor. It is our expression template lass, whi h is a ommon
umbrella for all dierent types of lambda fun tors. Pla eholder types, the
types of the results of dierent lambda expressions, su h as free1 + 1 and
bind(foo, 1, free2, free1), ex eption handling lambda fun tors et . are
all instan es of the lambda_fun tor template.

5.1 Lambda expressions as partial fun tion appli ations


The LL implementation is best understood by viewing lambda expressions as
delayed and partial fun tion appli ations. In a partial fun tion appli ation,
a fun tion is alled with empty slots, pla eholders, in the argument list.
Hen e, some formal parameters of the fun tion are bound normally to the
a tual arguments, while some parameters are left open. The result of su h
a partial fun tion all is another fun tion with possibly fewer (or more, see
below) arguments. When this new fun tion is alled with the arguments that
were left open, the original fun tion is alled with an argument list where
these arguments have been substituted for the pla eholders. For example,
the expression free1 = 1 is a partial appli ation of the binary assignment
fun tion, where the rst argument is left open and the se ond argument
is bound to 1. When this unary partially applied fun tion is alled with
some argument x, free1 gets repla ed and the original operator= fun tion
is alled as x = 1.
The model of partial fun tion appli ation must be generalized a bit in
order to ompletely des ribe the fun tionality of the LL: in addition to a onstant value, an argument an be bound to another partially applied fun tion.
For example, in the expression -(free1 * free2) the argument of the unary
operator- fun tion is bound to a partially applied fun tion free1 * free2.
Note that in this example, the partial appli ation of the unary operatorresults in a binary fun tion. Furthermore, in the LL a zero-argument (see
the example in se tion 2.1) fun tion an be a target of a partial appli ation.
This means that the fun tion all is simply delayed.

5.2 The role of tuples


The parameters of the lambda_fun tor template des ribe the type of the
delayed operator/fun tion and the types of the bound arguments and pla e21

holders. Although the number of bound and unbound arguments an vary,


the lambda_fun tor parameterization overs all ases (there are a few spe ializations for lambda_fun tor though). This is possible be ause of tuples.
We wrap the parameters inside a tuple obje t and an thus treat the parameter list as one entity, regardless of the true number of parameters. Obviously,
at some point we must break up the tuples and repeat some ode for dierent
argument list lengths in order to be able to all the underlying fun tions.
However, thanks to tuples this ode repetition an be kept to a minimum.
In short, a tuple is a generalization of the standard pair template. An
arbitrary number of obje ts of arbitrary types an be grouped into a tuple.
For example, the following denitions are valid tuple types:

tuple<int, onst float, string, onst double*>


tuple<int (*) (int, double), tuple<A, B>, onst har&, C&>
Hen e, value types, onst and non- onst types, referen e types, other tuples,
fun tion pointers et . are valid element types. With the ex eption of some
unavoidable restri tions (e.g. non- opyable types annot be stored in a tuple
ex ept as referen es), any C++ type an be used as a tuple element type.4
Tuples an be onstru ted expli itly:

tuple<int, double, A>(1, 3.14, A())


or with the make_tuple fun tion ( f. std::make_pair):

make_tuple(1, 3.14, A())


The Nth element of a tuple, where N is an integral onstant, an be a essed with no runtime overhead using the get fun tion as get<N>(x) or
x.get<N>(). The two alternatives are inter hangeable. The type of a tuple
element an be queried with the traits lass tuple_element_type. The type
expression:

tuple_element_type<N, T>::type
retrieves the type of the Nth element of a tuple of type T.
To use the LL, you do not have to know anything about tuples. To understand the implementation, the above should be enough. For an interested
reader, tuples are des ribed in greater depth in [Jr01, Jr99a, Jr99b.
Note that our urrent implementation of tuples has an upper limit of
10 arguments. When we mention a limit on the number of ases in the LL
as being 10 or less, this is where the limitation originates. We ould easily
in rease this number but for now it has been a reasonable upper limit.
4

Our urrent implementation does not support volatile-qualied types.

22

5.3 Pla eholders


In the rst part of the arti le we went over how to use pla eholders. Here
we explain the internals of what they are. Pla eholders in the LL are just
instan es of a templated lass pla eholder wrapped in a lambda_fun tor:

template <int I> lass pla eholder {};


typedef onst lambda_fun tor<pla eholder<FIRST> > free1_type;
typedef onst lambda_fun tor<pla eholder<SECOND> > free2_type;
typedef onst lambda_fun tor<pla eholder<THIRD> > free3_type;
We have dened pla eholder<FIRST> to be the pla eholder for the rst argument to the lambda fun tor, pla eholder<SECOND> the se ond argument
and so forth. The onstants FIRST, SECOND and THIRD are propagated from
pla eholder types to more omplex lambda fun tors and arry information
about the arity of the lambda fun tors. Consequently, we all them arity
odes (see se tion 5.3.1).
Noti e that the pla eholder has no knowledge of the type of the argument
it stands for. As we explained in se tion 2.2, a lambda fun tor an be alled
with arguments of any types, provided that this results in a valid all to the
underlying fun tion after the argument substitutions have been performed.
This is possible, sin e lambda fun tor templates dene the operator() fun tions as member templates. The argument types are not known until the
fun tor is a tually alled and impli it instantiation of the operator() member template is performed.
When the ompiler has dedu ed these argument types, two re ursively alternating dedu tion hains ommen e. The ompiler must dedu e the return
type of the operator(). This is performed with the return type dedu tion
templates of the LL. As input, the return type dedu tion system takes the
underlying operator, i.e. the target fun tion, of the lambda fun tor together
with the types of the arguments this operator will be alled with. In order
to gure out the argument types, the ompiler may have to resort to the
return type dedu tion system re ursively. We'll rst demonstrate a simple
non-re ursive ase:

int x;
...
(free1 = 1)(x);
First, the ompiler dedu es the argument types to the underlying assignment
operator; the instantiation be omes operator=(int&, onst int&). Next,
the return type is dedu ed based on the information that we are performing
an assignment operator between arguments of type int.
23

Fun tion ompositions ause a bit more of a heada he (for the implementers of the library, not for the user). For example, onsider the lambda
expression, free1 + (free1 * free2). Suppose this binary fun tion is
alled with obje ts of type A and B. Before the ompiler knows the se ond
argument type for the operator+, it must perform the return type dedu tion
for multipli ation of types A and B. Say it results in a type C. Then the ompiler knows that the addition is for types A and C, and an pro eed with the
type dedu tion for that operation. As omplex as this is, the LL orre tly dedu es return types for su h ompositions. The return type dedu tion system
is explained in more details in se tion 6.7.
5.3.1

Arity odes

Ea h lambda fun tor has an arity ode as one of its template arguments.
When a lambda expression is evaluated, the resulting lambda fun tor gets
the highest arity ode from its subexpressions. In other words, the arity ode
of a lambda fun tor states the highest pla heholder index that is present in
any of the subexpressions. For example:

-free1
// arity ode = FIRST
free1 - free2
// arity ode = SECOND
free1 - free2 - free3 // arity ode = THIRD
There is a dire t mapping from the arity odes to the a tual arities of the
lambda fun tors: FIRST means a unary fun tor, SECOND a binary fun tor
et . Note parti ularly, that even in the absen e of pla eholders with a lower
arity ode, the pla eholder with the highest arity ode determines the arity
of the lambda fun tor. For example, the lambda expression &free3 reates a
3-argument lambda fun tor. When alled, this lambda fun tor dis ards the
rst two arguments and returns the address of the third argument.
Note that arity ode an have the value NONE as well, whi h means that
the lambda fun tor is nullary, and the value EXCEPTION, whi h indi ates that
the freeE pla eholder was used in a lambda expression.5

A walk through the layers

We have now overed the basi s and an guide you through the dierent
layers of the LL  with the simple lambda expression we promised. Our
example lambda expression is free1 < 0. This expression reates a unary
lambda fun tor, whi h ompares its argument to zero.
5
There is still one more onstant (RETHROW), whi h an be bitwise ored with other arity
odes. It is used internally to guarantee that a rethrow lambda expression is only used
within a at h blo k.

24

6.1 Overloaded operators for lambda expressions


For ea h binary operator op, there are three overloaded denitions:

lambda_fun tor op any_type


any_type
op lambda_fun tor
lambda_fun tor op lambda_fun tor
For ea h unary operator, one overloaded denition is enough.
When the ompiler en ounters our example expression, the fun tion template shown in gure 2. is sele ted as a result of the overload resolution. This
operator takes a lambda_fun tor as the left-hand argument and onstru ts
another lambda_fun tor. The template parameters Arg and B get the values pla eholder<FIRST> and int, and the type of the onstru ted lambda
fun tor be omes:

lambda_fun tor<
lambda_fun tor_args<
a tion<2, relational_a tion<less_a tion> >,
tuple<lambda_fun tor<pla eholder<FIRST> >, onst int>,
FIRST
>
>
This type arries information about the invoked operator, the types of the
arguments to the operator, and the arity of the lambda fun tor. The type
a tion<2, relational_a tion<less_a tion> > means that the lambda
fun tor stands for the binary less than operator (see se tion 6.4 about a tion
lasses). The arguments free1 and 0 are stored in a tuple, whi h gets the
type tuple<lambda_fun tor<pla eholder<FIRST> >, onst int>. The
dig_arity is a template whi h extra ts the arity ode of a lambda fun tor. The arity ode of free1 is FIRST (see se tion 5.3.1). As the onstant
0 has no arity ode, the value FIRST is propagated to the resulting lambda
fun tor, stating that its fun tion all operator is unary.
Note that we need three template arguments to store the above pie es of
information. The lambda_fun tor_args is an intermediate template that
groups these three parameters into a single lass. Due to this arrangement the lambda_fun tor template has only one template parameter, whi h
makes overloading operators and fun tions for lambda fun tors simpler.

6.2 The lambda_fun tor template


The lambda_fun tor templates dene the fun tion all operators that are
alled from STL algorithms. There are four lambda_fun tor spe ializations
for dierent arities (0-3 arguments). In addition to this, lambda_fun tor
has a ommon spe ialization for the three pla eholder types and one more
25

template< lass Arg, lass B>


inline onst
lambda_fun tor<
lambda_fun tor_args<
a tion<2, relational_a tion<less_a tion> >,
tuple<lambda_fun tor<Arg>, onst B>,
dig_arity<Arg>::value
>
>
operator< ( onst lambda_fun tor<Arg>& a, onst B& b)
{
return lambda_fun tor<
lambda_fun tor_args<
a tion<2, relational_a tion<less_a tion> >,
tuple<lambda_fun tor<Arg>, onst B>
dig_arity<Arg>::value
>
> ( make_tuple(a, b) );
}

Figure 2: The operator< overloaded for a lambda expression with a lambda


fun tor as the left-hand argument.

26

template < lass A tion, lass Args>


lass lambda_fun tor<lambda_fun tor_args<A tion, Args, FIRST> >
: publi lambda_fun tor_base<A tion, Args> {
publi :
expli it lambda_fun tor( onst Args& args) :
lambda_fun tor_base<A tion, Args>(args) {}
template< lass A>
typename return_type<lambda_fun tor, A&, nil, nil>::type
operator()(A& a) onst {
return this->template all<
typename return_type<lambda_fun tor, A&, nil, nil>::type
> (a, onst_nil(), onst_nil());
}
template< lass RET, lass A>
RET ret_ all(A& a) onst {
return
this->template all<RET>(a, onst_nil(), onst_nil());
}
};

Figure 3: The spe ialization for unary lambda fun tors. The template keyword before the all invo ation is required by the C++ standard; it helps
the parser to gure out that all is a templated member fun tion rather
than a data member.
whi h is used with delayed ex eption handling for a ombined total of 6
partial spe ializations.
Sin e our example lambda expression has one unbound parameter, and
the arity ode is thus FIRST, a lambda_fun tor spe ialization with a unary
operator() template is instantiated (gure 3.). The task of the operator()
fun tion template is to initiate the return type dedu tion hain and forward
the all further. The return type dedu tion traits, as well as the fun tions
that substitute the pla eholders for the a tual arguments are only implemented for the ase of 3-argument lambda fun tors. Hen e, in this unary
ase the se ond and third arguments are lled with spe ial nil-obje ts.
Suppose that the lambda fun tor in gure 3. is alled with an argument
of type int. For example:

int i; ... ; (free1 < 0)(i);


27

Now the template parameter A be omes int and the

return_type<lambda_fun tor, int&, nil, nil>::type


an be resolved. We'll onsider the return type dedu tion traits later in
se tion 6.7, just assume for now that the return type is orre tly dedu ed to
bool, or a tually to onst bool.6
After lling the missing arguments with nil-obje ts ( onst_nil()) does
this), all arguments are forwarded to the all fun tion dened in the base
lass lambda_fun tor_base.
In addition to the fun tion all operator, the lambda_fun tor templates
dene another member fun tion. This ret_ all fun tion has the same fun tionality as the fun tion all operator, ex ept for the return type whi h is
given as an expli itly spe ied template argument, rather than dedu ed with
the return type dedu tion traits. The LL uses ret_ all in internal alls to
lambda fun tors to prevent the ompiler from doing the same return type
dedu tions repeatedly.

6.3 The lambda_fun tor_base template


The lambda_fun tor_base templates store the argument tuple and dene
the all fun tions whi h perform the argument substitution. The template
is spe ialized for ea h dierent length of fun tion argument list; while the
varying fa tor in lambda_fun tor spe ializations was the arity of the lambda
fun tor, here it is the arity of the target fun tion.
Our example target fun tion, operator<, is binary. Therefore the spe ialization shown in gure 4. is sele ted. As a result of the all from the
lambda_fun tor::operator() fun tion, the all member template gets instantiated as:

onst bool all(int& a, onst nil& b, onst nil& )


This fun tion delegates the argument substitution to the sele t fun tions
(see se tion 6.5) and forwards the substituted argument list to A t::apply
fun tion; in this ase to relational_a tion<less_a tion>::apply.7

6.4 A tion lasses


A tion lasses are wrappers for the target fun tions. There is one for ea h
overloaded operator (less_a tion, minus_a tion et .). There are also a tion lasses for dierent types of fun tions: fun tion pointers, pointers to
6
In order ompose lambda fun tors we return temporaries as onst types. This allows
us to pass the result of one lambda fun tor to another lambda fun tor as a referen e
argument.
7
Note that the return type dedu tion is only performed in the lambda fun tor's
operator(). The dedu ed return type is then forwarded as an expli itly spe ied template parameter both to the lambda_fun tor_base:: all and further to the A t::apply
fun tion. This arrangement lessens the load of the ompiler signi antly.

28

template< lass A t, lass Args>


lass lambda_fun tor_base<a tion<2, A t>, Args> {
Args args; // the argument tuple
publi :
expli it lambda_fun tor_base( onst Args& a) : args(a) {}

template< lass RET, lass A, lass B, lass C>


RET all(A& a, B& b, C& ) onst {
return A t::template apply<RET>(
sele t(get<1>(args), a, b, ), // sele t substitutes
sele t(get<2>(args), a, b, )); // the arguments
}

Figure 4: The lambda_fun tor_base spe ialization for unary fun tors.
member fun tions et . The a tion lass is part of the type of the lambda
fun tor instantiation and its sole purpose is to provide a stati member fun tion for alling the target fun tion.
The a tion lasses are separated into groups. The groups are dened
by similar operation, for instan e, arithmeti , logi al, boolean, void, et .
This grouping allows us to implement the return type dedu tion rules for
sets of operators. For instan e, all arithmeti operators follow the integral
promotion rules, while the return type of all relational a tions is onst bool.
Here is the ode for the less_a tion lass:

lass less_a tion {};


template < lass A tionClass> relational_a tion;

// The spe ialization of relational_a tion for less than

stru t relational_a tion<less_a tion> {


template< lass RET, lass A, lass B>
RET stati apply(A &a, B &b) { return a < b; }
};

The apply fun tion eventually alls the target fun tion, whi h in our example
is the built-in operator<.

6.5 Argument substitution


The apply fun tion is alled with a merged argument list, where the arguments have already been substituted for the pla eholders. The sele t
29

fun tions alled from lambda_fun tor_base templates perform this substitution. The all

sele t(get<1>(args), a, b, )
sele ts either the bound argument from the args tuple (get<1>(args)) or
one of the arguments a, b or . Whi h it does, depends on the type of the
bound argument.
The default ase is that we do not know anything about the type. This
means that we have a bound argument, and we return that argument as
su h:

template< lass Any, lass A, lass B, lass C>


inline Any& sele t(Any& any, A&, B&, C&) {
return any;
}
However, the stored argument an be a pla eholder, in whi h ase one of the
other arguments should be hosen. In our example, the following spe ialization for the free1 pla eholder overrides the default behavior by sele ting
the rst parameter of the lambda_fun tor_base:: all fun tion:

template< lass A, lass B, lass C>


inline A&
sele t( onst lambda_fun tor<pla eholder<FIRST> >&,
A& a, B&, C&) {
return a;
}
Analogous overloaded fun tions exist for all pla eholder types.
Fun tion de omposition is also a hieved with overloading sele t fun tions. Instead of just returning one of the arguments, the sele t fun tions
all the lambda fun tor with some of the other arguments, and return the
result of that all. We do not go into details, but the following pseudo ode
shows the general idea. This spe ialization would mat h for binary lambda
fun tors. Noti e the all to ret_ all rather than the fun tion all operator
(see se tion 6.2):

template< lass RET, lass A, lass B, lass C>


inline RET
sele t( onst binary_lambda_fun tor & op, A& a, B&, C&) {
return op.template ret_ all<RET>(a, b);
}
30

6.6 Intermediate summary of the example


We have now overed the following fun tion all hain:
1. The overloaded operator< reates a lambda_fun tor of type:

lambda_fun tor<
lambda_fun tor_args<
a tion<2, relational_a tion<less_a tion> >,
tuple<pla eholder<FIRST>, onst int>,
FIRST
>
>
2. The unary fun tion all operator of this lass is alled with the variable
i of type int. The return type is resolved to onst bool with the
instantiation return_type<lambda_fun tor, int&, nil, nil>. The
return type and the arguments are forwarded to the all fun tion of
the base lass:

lambda_fun tor_base<
a tion<2,relational_a tion<less_a tion> >,
tuple<lambda_fun tor<pla eholder<FIRST>, onst int>
>:: all< onst bool>(i, onst_nil(), onst_nil());
3. The all fun tion alls the sele t fun tion to hoose between the
bound arguments stored in the tuple member and the argument i
supplied as a parameter:
 The rst argument: sele t(get<1>(args), a, b, ) turns into
sele t(free1, i, onst_nil(), onst_nil()) whi h returns
a referen e to i.
 The se ond argument: sele t(get<2>(args, a, b, ) be omes
sele t(0, i, onst_nil(), onst_nil()) and returns 0.
4. The return type is dedu ed and the fun tion

relational_a tion<less_a tion>::apply< onst bool>(i, 0)


is alled, whi h returns the result of the expression i < 0 all the way
to the aller of the lambda_fun tor::operator().
31

template< lass A t, lass Args, int Code,


lass A, lass B, lass C>
stru t return_type<lambda_fun tor<a tion<2, A t>, Args, Code>,
A, B, C> {
typedef typename
return_type<typename tuple_element_type<1, Args>::type,
A, B, C>::type X;
typedef typename
return_type<typename tuple_element_type<2, Args>::type,
A, B, C>::type Y;
typedef typename return_type_2<A t, X, Y>::type type;
};

Figure 5: The binary fun tion ase of the topmost layer of the return type
dedu tion system.

6.7 Return type dedu tion


The return_type template onstitutes the topmost layer of the return type
dedu tion system. It takes four template parameters: a lambda fun tor
and the types of the three a tual arguments of the lambda fun tor. The
return_type template is spe ialized with respe t to the arity of the underlying a tion (1, 2, 3 and N argument ases). In our example, the a tion is
binary and thus the spe ialization in gure 5. is instantiated.
The template parameter A t is the a tion lass type and the Args tuple
type ontains the bound argument and pla eholder types. A, B and C are the
types of the parameters to substitute for the pla eholders. The denition an
be interpreted as follows: Use the traits lass return_type_2<A t, X, Y>
to dedu e the return type of performing the binary a tion A t to some parameters of types X and Y. The parameters X and Y are obtained re ursively
with the return_type template.
In our example, the a tion lass is relational_a tion<less_a tion>,
the rst tuple element type is lambda_fun tor<pla eholder<FIRST> > and
the se ond element type is onst int. Hen e, the spe ialization:

template < lass A, lass B, lass C>


stru t
return_type<lambda_fun tor<pla eholder<FIRST> >, A, B, C> {
typedef A type;
};
32

is used to dedu e that X equals to int. Y is dedu ed to onst int with the
primary template:

template < lass Arg, lass A, lass B, lass C>


stru t return_type {
typedef Arg type;
};
The return_type_2 template has spe ializations for dierent (binary)
a tions or a tion groups. All relational operators are dened to return
onst bool:

template< lass A, lass B, lass A t>


stru t return_type_2<relational_a tion<A t>, A, B> {
typedef onst bool type;
};
Above we stated that the return type dedu tion system is easily extendible. This is a hieved by adding spe ializations for the return_type_2
template. For example, the following spe ialization denes that the return
type of relational operators for a user-dened type FuzzyNumber would be
FuzzyTruthValue:

template< lass A t>


stru t return_type_2<relational_a tion<A t>,
FuzzyNumber, FuzzyNumber> {
typedef onst FuzzyTruthValue type;
};
Furthermore, the LL has a spe ial type promotion s heme for numeri types,
modeled after the one used in the Blitz library [Bli99. In this s heme ea h
type is given a value. The value of the result type of an arithmeti operation
is the largest of the parameter values, i.e. if short = 300 and long = 500,
then short op long is max(300,500) or 500, or long.
User dened types are added to the promotion s heme by assigning them
a value. For example, the result type of the addition of a FuzzyNumber and
any standard numeri type an be dened with the following template. Here
FuzzyNumber op any native type is promoted to FuzzyNumber; value is large
enough to get the orre t promotion weight.

template<> stru t promote_ ode<FuzzyNumber> {


stati onst int value = 2000;
};
33

6.7.1

Overriding return type dedu tion

For one-time-only uses it may be an overkill to dene the return type dedu tion traits for some user-dened types. As we stated, the return type
dedu tion system an be overridden and the return type spe ied on the y
within a lambda expression. This is done by wrapping the lambda expression inside a ret<T> fun tion, where T is the return type. For example, had
we not dened the above traits for FuzzyNumber lasses, we ould write as
follows:

ve tor<FuzzyNumber> fuzzy_numbers;
ve tor<FuzzyTruthValue> fuzzy_bools;
FuzzyNumber x;
...
transform(fuzzy_numbers.begin(), fuzzy_numbers.end(),
fuzzy_bools.begin(),
ret<FuzzyTruthValue>(x < free1));
Note that ret<T> does not all any of the C++ ast statements. It merely
denes the return type for a given lambda expression. The true return
type of the target expression must be ompatible with T, that is, impli itly
onvertible to T.

6.8 Bind expressions


In the pre eding se tions, we used the expression free1 < 0 to walk through
the entral parts of the implementation. Our example didn't over the implementation of the bind fun tions, whi h is the topi of this se tion.
The se tion 2.3 showed several examples of bind expressions. A bind
expression is a all to one of the overloaded bind fun tions, where the rst
argument is the target fun tion and the remaining ones are used as the
argument list, after substitutions, to the target fun tion when it is alled.
To begin with, the bind fun tion is overloaded to take any number of
arguments between zero and 10. Further, for ea h argument list length we
need four overloaded denitions to over the ases of non-member fun tion
pointers, pointers to onst and non- onst member fun tions and arbitrary
fun tion obje ts as target fun tions. As an example, the gure 6. shows the
3-argument bind fun tion that mat hes bind invo ations, where the target
fun tion is a pointer to a binary non-member fun tion. The following bind
expression is an example of a all that mat hes this bind fun tion.

int foo(float a, int b);


...
bind(&foo, free1, 1);
34

The a tion lass of the resulting lambda fun tor is fun tion_a tion<3>.
Compared to, say, the less_a tion, fun tion a tion lasses have one extra
layer to unify the syntax of alling dierent types of fun tions (member vs.
non-member) before the a tual apply fun tion is alled. Note that although
the target fun tion is binary, the arity of the a tion lass is 3, sin e the
a tion lass treats the target fun tion as a normal substitutable argument.
The bind fun tion groups the target fun tion with the other arguments into
a tuple whi h is stored in the lambda fun tor.
Note the type expression ombine_arities<Arg1, Arg2>::value. In
the above example ase the target fun tion is a pointer to a fun tion, and
thus not a lambda fun tor. However, the remaining two arguments to the
bind fun tion an be anything, parti ularly they an be lambda fun tors.
The ombine_arities traits lass omputes the arity ode of the lambda
fun tor type to be reated from the arity odes of the argument types; slightly
simplied it sele ts the highest arity ode of its argument types.
We need the bind fun tions to implement the interfa e for reating
lambda fun tors from various types of fun tions, and the fun tion_a tion
templates to implement the alling rules for these fun tions. Further, we
need some templates for the return type dedu tion system. However, as ribe to the orthogonal layers in the LL, these are the only omponents that
are spe i to bind expressions. Other template layers are not dependent on
the type of the target fun tion in the lambda fun tor.
This on ludes our dis ussion about the implementation of the basi
features of the Lambda Library.

Spe ial ases in operator overloading

7.1 Pointer to member operator


In se tion 3.2 we stated that the argument of the operator->*, the member
pointer, an refer to either a member obje t or a member fun tion. Consequently, the global operator->* is overloaded dierently for these ases. If
the member pointer is an obje t, we return a lambda fun tor with a referen e
to that obje t. If the member pointer is a pointer to a fun tion, the C++ language doesn't give mu h freedom what to do next. The result is something
like a pending member fun tion all; the only thing that an be done with the
result is to pass it a list of arguments (see [Mey99 for an in-depth analysis of
overloading operator->*). Hen e, we return a temporary obje t, whi h has
the fun tion all operator dened for the same number of arguments as does
the member pointer fun tion. This member_ptr_lambda_fun tor serves as
a proxy. It is not a lambda fun tor that gets passed to an STL algorithm,
but rather the fun tion all operator of the member_ptr_lambda_fun tor
reates and returns that lambda fun tor.
In se tion 3.2 we showed the all free1->*(&data::foo) to the unary
35

template < lass Par1, lass Par2, lass Result,


lass Arg1, lass Arg2>
inline onst
lambda_fun tor<
lambda_fun tor_args<
a tion<3, fun tion_a tion<3> >,
tuple<Result (*)(Par1, Par2), onst Arg1, onst Arg2>,
ombine_arities<Arg1, Arg2>::value
>
>
bind(Result (* onst & f)(Par1, Par2),
onst Arg1& a1, onst Arg2& a2) {
return
lambda_fun tor<
lambda_fun tor_args<
a tion<3, fun tion_a tion<3> >,
tuple<Result (*)(Par1, Par2), onst Arg1, onst Arg2>,
ombine_arities<Arg1, Arg2>::value
>
> (make_tuple(f, a1, a2));
};

Figure 6: The bind fun tion for the ase where the target fun tion is a
pointer to a binary non-member fun tion.

template< lass Arg, lass Result, lass Obje t, lass Arg1>


inline onst
member_ptr_lambda_fun tor
<1, Arg, Result (Obje t::*)(Arg1) onst, Arg1>
operator->*( onst lambda_fun tor<Arg>& a,
Result(Obje t::* onst & f)(Arg1))
{
return member_ptr_lambda_fun tor
<1, Arg, Result(Obje t::*)(Arg1), Arg1>(f, a);
}

Figure 7: Member pointer operator for unary non- onst member fun tions.

36

member fun tion int data::foo(int). The gure 7 shows the overloaded
denition of operator->* whi h gets instantiated in this ase. The reation
of the member_ptr_lambda_fun tor proxy in this fun tion triggers the instantiation of the spe ialization shown in gure 8. We do not show the
ode of the inherited template lass member_ptr_base; it is just a holder of
the arguments, the lambda fun tor and the member pointer, passed to the
overloaded operator->*.
In the example of se tion 3.2 we passed the onstant 5 to the result of the
operator->* all with the expression (free1->*(&data::foo))(5). This is
a all to the operator() of the member_ptr_lambda_fun tor, and it results
in the lambda fun tor that an be passed to an STL algorithm.
Furthermore, we stated that the same fun tionality an be a hieved using
the bind fun tion. Indeed, the arguments are bound into a tuple the same
way that bind does it and the a tion lass is fun tion_a tion. This is the
same a tion that is used by bind. It does not make a dieren e whi h syntax
is used; both interfa es reate the exa t same lambda fun tor instantiation.

7.2 omma, && and || operators


The omma operator and the logi al and and or operators follow the C++
rules for argument evaluation order and short- ir uiting. Instead of implementing these operators with an a tion lass like less_a tion and alling
the a tion lass from the lambda_fun tor_base template, we all the operator dire tly from lambda_fun tor_base. For this, we need to spe ialize
the lambda_fun tor_base for ea h of these operators. This guarantees that
only the ne essary expressions will be evaluated and in the orre t order.
Another reason for implementing the omma operator in this way is that the
built-in omma operator a epts a void argument. We annot all a fun tion, parti ularly the apply fun tion of an a tion lass, with an argument of
type void. Therefore we must all the omma operator dire tly.
As an example, the spe ialization of the lambda_fun tor_base for the
logi al and operator is shown in gure 9. Note that the operator&& is alled
dire tly in the all fun tion of lambda_fun tor_base. The && alling rules
apply; the right-hand sele t fun tion is never alled if the left-hand sele t
returns false.

7.3 Conditional expression


The onditional expression ?: is not allowed to be overloaded. Besides this,
the result type rules for this operator are very omplex (see [C++98, se tion
5.16). Nevertheless, the LL ounterpart for this operator that we showed
in se tion 3.4, the if_then_else_return fun tion template, follows these
rules to the extent that it is possible. Rather than spend time dis ussing
how this bit of the library works, we are going to skip it. The return type
37

template< lass Arg, lass Obje tMemPtr, lass Arg1>


lass member_ptr_lambda_fun tor<1, Arg, Obje tMemPtr, Arg1> :
private member_ptr_base<Arg, Obje tMemPtr> {
publi :
typedef member_ptr_base<Arg Obje tMemPtr> inherited;
member_ptr_lambda_fun tor(Obje tMemPtr onst &f,
onst lambda_fun tor<Arg> &a) :
inherited(f,a) {}

};

lambda_fun tor<
lambda_fun tor_args<
a tion<3, fun tion_a tion<3> >,
tuple<Obje tMemPtr, lambda_fun tor<Arg>, onst Arg1>,
ombine_arites<Arg, Arg1>::value
>
>
operator()( onst Arg1 &a1) onst {
return
lambda_fun tor<
lambda_fun tor_args<
a tion<3, fun tion_a tion<3> >,
tuple<Obje tMemPtr, lambda_fun tor<Arg>, onst Arg1>,
ombine_arites<Arg, Arg1>::value
>
>
(
make_tuple(inherited::m_memPtr,
inherited::m_lambdafun tor,
a1)
);
}

Figure 8: The member_ptr_lambda_fun tor spe ialization for unary non onst member fun tions.

38

template< lass Args>


lass lambda_fun tor_base<
a tion<2, logi al_a tion<and_a tion> >, Args
> {
Args args;
publi :
expli it lambda_fun tor_base( onst Args& a) : args(a) {}
template< lass RET, lass A, lass B, lass C>
RET all(A& a, B& b, C& ) onst {
return sele t(get<1>(args), a, b, ) &&
sele t(get<2>(args), a, b, );
}
};

Figure 9: lambda_fun tor_base template spe ialization for the logi al


operator.

and

dedu tion is omplex and only narrowly interesting as a te hnique. We invite


the interested reader to read the library ode dire tly.

Control onstru ts

To be able to provide the exa t same fun tionality, that is the exa t same
argument evaluation order, to the underlying C++ ontrol onstru ts, the
LL ontrol onstru ts are again spe ializations of the lambda_fun tor_base
lass.
As an example, we show the lambda_fun tor_base spe ialization for the
for_loop in gure 10. Note that the return type is a spe ialization to return
void, therefore no return statement is ne essary. Sin e the sele t fun tions
are written inside the for loop, the ode will obey the regular C++ rules of
a for loop. All loop ontrols in the LL are implemented analogously.

8.1 Swit h as a lambda expression


For the swit h statement we go into a bit more detail. The swit h lambda
expression is a omposition of several lambda expressions, and we show the
te hniques to ensure that the omponent lambda expressions are of the right
kind. It is a bit hairy but we try our best to explain the whys and the whats.
To implement the swit h lambda expression we have spe ialized the
swit h_statement fun tion for up to 9 ase statements. These fun tions
39

template< lass Args>


lass lambda_fun tor_base<
a tion<4, return_void_a tion<forloop_a tion> >, Args
> {
Args args;
publi :
expli it lambda_fun tor_base( onst Args& a) : args(a) {}
template< lass RET, lass A, lass B, lass C>
RET all(A& a, B& b, C& ) onst {
for(sele t(get<1>(args), a, b, );
sele t(get<2>(args), a, b, );
sele t(get<3>(args), a, b, ))

};

sele t(get<4>(args), a, b, );

Figure 10: lambda_fun tor_base spe ialization for the for loop.
return a lambda_fun tor with a swit h_a tion as the a tion lass, and a
tuple holding the dierent ases. Also the lambda_fun tor_base templates
are spe ialized dierently for ea h number of ase statements. A tually, there
are two spe ializations for ea h su h ase: one with the last ase being the
default statement, and one without the default statement. The underlying
swit h statement ode, the one that alls the built-in swit h statement, is
built using one of these spe ializations.
Again, we use a simple example to walk through the dierent parts of the
implementation. Consider the following swit h lambda expression onsisting
of a single ase and a default:

swit h_statement(free1,
// the test
ase_statement<0>(free1 = 100), // ase 1:
default_statement(free1--));
// default:
The dierent ase statements and the default statement are lambda expressions, but they would not make any sense outside of a swit h statement.
Further, the swit h lambda expression would not make any sense, if instead of ase lambda expressions, the arguments would be arbitrary lambda
expressions. Consequently, we have dened a swit her lass, whi h is a
lambda fun tor that an only be used as a ase of a swit h_statement. The
swit her lass inherits from a lambda_fun tor instantiation, thus swit her
40

has all the properties of a lambda fun tor. In order to redu e the number of
spe ial ases, the same template overs both regular ases and the default
ase. A template parameter is used to indi ate whi h of these types the ase
statement is:

enum CaseEnumType { CASE, DEFAULT };


template<CaseEnumType aseType, int CaseValue, lass Args>
lass swit her : publi lambda_fun tor<Args> {
publi :
template< lass T>
swit her( onst lambda_fun tor<T>& a)
: lambda_fun tor<Args>(a) {}
};
This next template is a fun tion for setting up the swit her lass for a regular ase statement. Noti e that the CaseValue template parameter is not
dedu ible; it is the ase value that the user spe ies expli itly within the all
to this fun tion:

template<int CaseValue, lass Arg>


onst swit her<CASE, CaseValue, lambda_fun tor<Arg> >
ase_statement( onst lambda_fun tor<Arg> &arg) {
return swit her<CASE, CaseValue, lambda_fun tor<Arg> >(a);
}
The fun tion for setting up the swit her lass for a default ase statement is
similar, ex ept for the dierent ase type parameter (DEFAULT), and for the
fa t that the ase value is already set to a known value (0 is an arbitrary
hoi e):

template< lass Arg>


onst swit her<DEFAULT, 0, lambda_fun tor<Arg> >
default_statement( onst lambda_fun tor<Arg> &arg) {
return swit her<DEFAULT, 0, lambda_fun tor<Arg> >(a);
}
In order to ombine multiple swit her obje ts into one obje t we have
reated a template to hold a group of them. This lass allows us to treat
all variations of the swit h as one type for return type dedu tion purposes,
the advantage being that we already have a return_void_type lass that
we an reuse:

template< lass ase1, lass ase2 = nil, lass ase3 = nil,


... , lass ase 9 = nil>
lass swit h_list_a tion;
41

template < lass TestArg, int Case1, lass Swit hA t1,


CaseEnumType CaseType2, int Case2, lass Swit hA t2>
inline onst
lambda_fun tor<
lambda_fun tor_args<
a tion<3, return_void_a tion<
swit h_list_a tion<
swit her<CASE, Case1, Swit hA t1>,
swit her<CaseType2, Case2, Swit hA t2>
>
>
>,
tuple<lambda_fun tor<TestArg>, Swit hA t1, Swit hA t2>,
ombine_arities<TestArg, Swit hA t1, Swit hA t2>::value
>
>
swit h_statement(
onst lambda_fun tor<TestArg>& a1,
onst swit her<CASE, Case1, Swit hA t1>& a2,
onst swit her<CaseType2, Case2, Swit hA t2>& a3)
{
return
lambda_fun tor<
lambda_fun tor_args<
a tion<3, return_void_a tion<
swit h_list_a tion<
swit her<CASE, Case1, Swit hA t1>,
swit her<CaseType2, Case2, Swit hA t2>
>
>
>,
tuple<lambda_fun tor<TestArg>,
Swit hA t1, Swit hA t2>,
ombine_arities<TestArg,
Swit hA t1, Swit hA t2>::value
>
>
(make_tuple(a1, a2, a3));
}

Figure 11: The swit h_statement.


42

Next, the overloaded swit h_statement fun tion for two ases is shown
in gure 11. The fun tion onstru ts the omplex lambda_fun tor whi h
ontains a tuple of the arguments, and en odes the underlying C++ swit h
statement within its template arguments. The use of return_type_void
indi ates that swit h ontrol lambda fun tors have no return type. The
template ombine_arities looks at the three lambda fun tors (the swit h
expression part, the rst ase statement and the default ase) for the highest
arity ode and sets the arity ode to that number (see se tion 6.8). This
arity ode determines whi h spe ialization of the lambda_fun tor template
we will use. In this ase the arity is FIRST, whi h means we an use the same
lambda_fun tor lass as we used for the example expression free1 > 0 (see
se tion 6).
Noti e that although we use the generi swit her lass as two of the
arguments, we do not allow a default statement in any pla e but the last
argument. Furthermore, we do not allow any other lambda expression types
as swit hers. Note also that the swit hers are stored into the argument tuple of the resulting lambda expression as normal lambda fun tors; the fa t
that they really are, or were, swit hers is deliberately lost at this point. The
purpose of the swit her template is to guarantee that the swit h lambda expressions are omposed of the right kinds of omponents. When the lambda
fun tor for the swit h statement has been onstru ted, this has been guaranteed and the swit hers are not needed anymore (they do prevail in the
a tion lass type, however).
The instantiation of the lambda_fun tor lass triggers the sear h for
a mat hing lambda_fun tor_base. The LL has a set of spe ializations of
the lambda_fun tor_base for swit h statements. The ompiler pi ks one of
these as the losest mat h and ontinues the instantiation. The gure 12.
shows the spe ialization of lambda_fun tor_base for a swit h statement
with one ase statement and the default ase. This ode expands similarly
to the for loop, with the sele t alls looking for pla eholders and the return type RET being set to void. The end result is a regular C++ swit h
statement.8
We used a dierent template to solve ea h layer of the problem. By
using all these templates we have rea hed a solution, where the size of the
ode is linear, rather than exponential, with respe t to the number of ase
statements we support. We an handle any ombination of pla eholders, and
any lambda expression, even another swit h lambda expression, as part of
the swit h lambda expression.
The do_on e implementation is analogous to the swit h_statement implementation: we have 9 overloaded do_on e fun tion templates, and another 9 spe ializations of the lambda_fun tor_base, one for ea h number of
ases.
8

Whew! What a lot of templates just to get ba k to plain old C++ ode.

43

template< lass Args, int Swit hCase1, lass Swit hArg1,


lass Swit hArg2>
lass lambda_fun tor_base<
a tion<3, return_void_a tion<
swit h_list_a tion<
swit her<CASE, Swit hCase1, Swit hArg1>,
swit her<DEFAULT, 0, Swit hArg2>
>
>
>,
Args
>
{
Args args;
publi :
expli it lambda_fun tor_base( onst Args& a) : args(a) {}
template< lass RET, lass A, lass B, lass C>
RET all(A& a, B& b, C& ) onst {
swit h(sele t(get<1>(args), a, b, ))
{
ase Swit hCase1:
sele t(get<2>(args), a, b, ); break;
default:
sele t(get<3>(args), a, b, ); break;
}
}
};

Figure 12: The lambda_fun tor_base spe ialization for a swit h statement
with one ase statement and the default statement.

44

Delayed ex eption handling

The ex eption handling lambda expression onsists of one try blo k lambda
expression and potentially many at h blo k lambda expressions. The at h
blo k an either be for a spe i ex eption type, or for any ex eption type
( f. the at h(...) statement), if it is the last at h blo k. Hen e, the basi
stru ture is the same as in the swit h lambda expression.
However, while the return type of a swit h lambda fun tor is void, the
ex eption handling lambda fun tor an return a value.9 This makes things
di ult, sin e the lambda fun tor an return either from the try part or
from one of the handlers. A try blo k lambda fun tor an either return
an argument, throw or return void, and a at h blo k lambda fun tor an
throw, or rethrow, or exit normally and return a value, or void. In all these
ombinations, the return types must mat h. In the ase of a normal exit from
a at h blo k, the at h blo k lambda fun tor must return a type whi h an
be onverted to the same type as the return type of the try blo k lambda
fun tor. On the other hand, if the at h blo k always throws, or rethrows,
the return types an be dierent as the at h blo k return type is never used.
Our rst attempt to over all these dierent ases had six spe ializations
for one at h blo k, for the two at h blo ks ase we had 12 spe ializations,
the next 24 and we stopped.10 We added some extra template layers to
he k stati ally whether the lambda fun tor in a at h blo k will throw or
not and got rid of the ombinatorial explosion of the template spe ializations.
Currently there are two ases per number of at h blo ks, one in whi h the
last at h is a at h(Type&), and the other in whi h it is a at h(...).
Rather than go through the whole gory ode we are going to wave our
hands and just tou h on the highlights. If you are really interested we suggest that you get the sour e and take a look. Again the implementation
relies on spe ializations: two lambda_fun tor_base spe ializations for ea h
number of at h blo ks. There is a at h_ex eption fun tion that returns a
template at her, whi h is similar to the template swit her. Ea h at her
holds a at h blo k lambda fun tor.
The try_ at h fun tions are similar to the swit h_statement fun tions.
The rst argument is an arbitrary lambda fun tor, and the following arguments are at her templates. Only the last argument an be a at h_all.
The spe ializations of the lambda_fun tor_base are similar to the spe ializations for the swit h_statement, with one extra template layer in the
all fun tions. There is again an upper limit of 9 at h lambda expressions
be ause of the limit of the number of arguments to the tuples: 9 ex eption
handlers seems more than adequate.
We take a glimpse of the ode with a simple example: The following ode
9
Hen e, an ex eption handling lambda expression an be used just as any lambda
expression, i.e. as a subexpression of another lambda expression.
10
Ok, we didn't but we should have, 48 spe ializations was really ridi ulous.

45

alls a fun tion foo on every element in some ontainer v. If foo throws an
ex eption of type exp_type, it gets aught and a message is sent to out:

void foo(int); // our fun tion foo.


for_ea h(v.begin(),v.end(),
try_ at h(
bind(foo, free),
at h_ex eption<ExpType>( out << "Caught it.")
)
);
The ode expands to use the spe ialization of the lambda_fun tor_base
shown in gure 13. Note the extra layer in the all member fun tion of
the at h blo k. Instead of alling the sele t fun tions dire tly, we all
another all fun tion11 , whi h is a stati member of the return_or_throw
template. This template alls the sele t fun tions, and handles the two
possible out omes: either the ode will throw again, or it must return an
argument that an be onverted to type RET.
Noti e that inside of the at h blo k the third argument to the all
fun tion is the ex eption (the parameter e) that was thrown, not the third
argument passed into the try blo k (the parameter ). The lient refers
to the third argument with free3 and to the ex eption obje t freeE as is
explained in se tion 3.8. The ex eption pla eholder takes the pla e of the
free3 pla eholder and thus reuses the ma hinery for pla eholder substitutions. This arrangement is the reason why the free3 pla eholder annot be
used within a at h blo k.

10

Performan e testing

The pre eding se tions show how the LL works, and how it is implemented.
In this se tion we fo us on the performan e issues. The news is both good
and bad.
Let us start with an example. The gure 14 shows a pie e of ode ontaining three variations of a loop performing identi al tasks: the rst uses
the LL, the se ond uses the lassi STL and the third is a traditional for
loop. When this example is timed using the g 2.95.2 on a Pentium 450
with basi -O3 optimizing on, the opy_if using the LL is slightly faster,
approximately 5%, than the lassi STL version. When the lambda ode is
timed against a hand oded for loop, it is slightly more than 2.4 times as
slow. If the array size is in reased to 16,000 elements to redu e the ee t of
the penalty of the extra fun tion all, lambda is only slightly more than 1.5
times as slow.
11

The LL may use inventive te hniques, but we an't say the same about fun tion names.

46

template< lass Args, lass Cat hType1>


lass lambda_fun tor_base<
a tion<2, try_ at h_a tion<
at h_list_a tion<Cat hType1> > >,
Args
>
{
Args args;
publi :
expli it lambda_fun tor_base( onst Args &a) : args(a) {}
template<RET, lass A, lass B, lass C>
RET all(A&a, B&b, C& ) onst
{
try
{
return sele t(get<1>(args), a, b, );
}
at h(Cat hType1& e)
{
return
return_or_throw<
RET,
typename tuple_element_type<2, Args>::type
>:: all(get<2>(args), a, b, e);
}
}
};

Figure 13: One of the lambda_fun tor_base spe ializations for ex eption
handling.

47

int v[100 = 100 values , vv[100;


onst int r1 = 30, r2 = 50;

// using lambda:

opy_if(v, &v[100, r1 <= free1 && free1 < r2);

// using lassi STL:

stru t in_range {
int imin, imax;
in_range(int x, int y) : imin(x), imax(y) {}
bool operator()(int i) { return imin <= i && i < imax; }
};
opy_if(v, &v[100, in_range(r1, r2));

// hand oded for loop:

register int onst * onst end = &v[100;


register int *pp = vv;
register int onst rr1 = r1;
register int onst rr2 = r2;
for(int *p = v; p<end;++p)
if (rr1 <= *p && *p < rr2) { *pp++ = *p; }

Figure 14: Three variations of the same loop.

48

This is the bad news. Now for the good news. When this same ode is
ompiled with KCC, known to be one of the best optimizing C++ ompilers,
the lambda ode is 5% faster than the hand written for loop!
When we add some more omplexity, hanging the omparison to:

opy_if(v, &v[100, r1 < (free1 + 3) && (free1 - 10) < r2);


using the g again, the lambda ode be omes 15% slower than the lassi
STL version, but ompared to the hand optimized version, is now slightly
faster at 2.3 times as slow.12
The good news from all of this is that with a reasonable optimizing
ompiler, using simple lambda expressions you are no worse o than using
lassi STL. The other good news is that with a great optimizing ompiler
there is no penalty at all.

10.1 Current ompiler limitations


The C++ Standard [C++98 suggests a template nesting level of 17 to help
dete t innite re ursion. Complex lambda templates an easily ex eed this
limit. Most ompilers allow a greater number of nested templates, but may
require you to in rease this limit expli itly via a ommand line argument.
The urrent implementation of the Lambda Library uses member templates and partial spe ialization of templates. They are all standard features
of the C++ language, but not all vendors support them at the time of writing this. We maintain a list of ompilers that an ompile the LL in our web
page http://lambda. s.utu.fi.

11

Relation to previous work

We've taken the operator overloading on ept from the Expression Template
library by Powell and Higley [PH00, the binding me hanism (see se tion 2.3)
from the Binder Library (BL) by Jrvi [Jr00. We have ombined, extended
and generalized these ideas to reate the Lambda Library.
Our implementation provides lambda fun tions whi h are both easier
to use and more general than the expression templates in the ET library.
The ET library used internally the STL template fun tors plus, minus et .
to handle the standard operators. These binary fun tors require that the
arguments and return obje t be of the same type. The ET library also
required that the user spe ify the type of the elements of the ontainer for
its pla eholders and de lare them before using them. These restri tions are
lifted in the LL. Further, the binding me hanism is more general than the
one des ribed in [Jr00. Spe i ally, even the target fun tion an be left
unbound (see se tion 2.3).
12

We know it is a small performan e gain but it is a gain.

49

Other related work in ludes the FACT [Fa 00 and FC++ [MS00 libraries, both developed oevally with the LL. The basi idea behind FACT
is quite similar to the LL, although the syntax of the lambda expressions is
dierent. Compared to LL, FACT supports a smaller set of operators. FACT
deliberately allows no side ee ts in lambda fun tions, whi h means that assignments and ontrol onstru ts are not supported. FACT lambda fun tions
are 'expression template aware' (see [SS00), while LL lambda fun tions are
not. Inspired by this feature in FACT, LL does provide a me hanism for
user extensions to enable the use of other expression template libraries, like
PETE [HCKS99, and Blitz++ [Bli99.
The FC++ is another library adding fun tional features to C++; it more
or less embeds a fun tional sublanguage to C++. A unique feature of FC++
is the possibility to dene variables for storing lambda fun tors. This is
very onvenient, even though the feature omes with some ost: the lambda
fun tor be omes dynami ally bound, and the return type and the argument
types of the lambda fun tor must be dened expli itly by the lient.
To our understanding, FACT and FC++ take the fun tional features
in a 'pure' form. The LL implements lambda fun tions, but does some
adjustments for a better t to C++. Our foremost goal is to provide lambda
fun tors whi h mat h perfe tly with the STL style of programming, not to
rigorously follow the fun tional programming paradigm.
FACT and FC++ introdu e some very innovative template te hniques
and we urge the reader to take a look at both of these libraries.

12

Con lusion

With the Lambda Library we hope to provide a valuable set of tools for
working with STL algorithms. The LL is quite a step forward from the
ET [PH00 and BL [Jr00 libraries, not to mention the standard binders
and fun tors.
The LL removes several restri tions and simplies the use of STL in many
ways. The users of the LL have a natural way to write simple fun tors leanly.
Tea hers of STL algorithms an have students writing lear ode qui kly:
it is easier to explain how to use the LL than the urrent alternative of
ptr_fun, mem_fun, bind1st, et . and it extends better to the more omplex
problems ( f. bind3rdAnd4th). Further, the LL introdu es a set of entirely
new possibilities for STL usage: The LL makes it possible to loop through
multiple nested ontainers. Ex eptions an be thrown, aught and handled
within the fun tor, and the looping in the STL algorithm an be ontinued.
Basi ally, all the above features are just subfeatures of a single language
onstru t, the lambda abstra tion. As it is not a built-in feature of C++, nor
will be in the near future, we hope you will nd omfort from the Lambda
Library.
50

12.1 Future Work


We are planning to add some new features to the LL, partly inspired by some
of the te hniques used in the FACT [Fa 00 and FC++ [MS00 libraries.
We are also on the lookout for ommon idioms, whi h ould benet from
be oming a lambda expression template and be in orporated into this library.
In any ase, the LL is still experimental, and thus subje t to hanges.
There has been some dis ussion about adding a typeof operator (for
querying a type of an expression stati ally) into the standard C++. This
is an open issue, but there is some support for this initiative in the C++
ommunity. The Lambda Library would really benet from a full ompiler
supported "typeof()" expression as it would eliminate a great deal of the
omplex return type dedu tion ode. We looked at using a template library
whi h emulates a true typeof operator [Gib00, but g 2.95.2 failed to ompile it. When g releases the next major update we will revisit this.
There are two gray areas whi h we haven't really looked at yet, but we
know we should, namely ex eption spe i ations in fun tion prototypes and
the volatile qualier. We may also onsider in reasing the number of allowed
arguments to tuples. For the most part, 10 arguments has been su ient
for our needs. Finally, there is work to do in porting the library to new
ompilers; we gladly wel ome volunteers.

A knowledgements
Jaakko and I met via the boost list server. I had been working on
expression templates and had not been satised with the implementation.
At the same time I dis overed Jaakko's bind library, Jaakko dis overed me.
The ex hange of emails was the mat h to the tinder. We shared papers, and
there has been no stopping us sin e. Jaakko wrote the ore of the Lambda
Library and re ognized the need for a multilayered approa h whi h we have
extended to isolate ea h problem. I suggested a number of additions to the
library whi h either I oded an initial attempt, some of whi h Jaakko xed,
and others he repla ed with a more robust implementation. Some ode is
from both of us, but in general, Jaakko did most of it. I've learned a lot
about using templates from Jaakko.
Gary's:

Starting from modest improvements to the standard binders,


we have steadily approa hed a proper, and e ient, lambda abstra tion. It
was truly interesting to dis over that standard C++ had all the ingredients
for it. All that was needed was template spe ialization, fun tion overloading
and some help from the ompiler (e.g. heavy inlining) and a lot of help from
Gary.
Gary's inexhaustible ow of new ideas has brought LL mu h further I
ould have ever imagined. I probably would have added ontrol stru tures
Jaakko's:

51

at some point, but ex eption handling is not something you would rst think
of. Gary's onstant help has been invaluable.
We are working with a 9-hour time dieren e, whi h has allowed us to
ode around the lo k. Thus the sum of two individuals working together is
greater than 2, it may even be 4. Also, we've worked together very hard for
quite some time and have a tually never met, so forgive us the abundan e
of mutual thanking.
Outside help:
No proje t exists in a va uum. We are indebted to those
who have gone before us and helped us along the way. The return type promotion ode of arithmeti types is modeled after that used in the Blitz++
library [Bli99. Andrei Alexandres u's lever ode for testing type ompatibility at ompile time is used at some parts of the return type dedu tion system. Valentin Bonnard suggested a way to globally overload the
operator->*() [Bon00. Jeremy Siek has helped us with the performan e
testing, reviewed the ode and ported it to KAI 3.4, and attempted a port
to MSVC 6.3.; unfortunately it proved to be impossible. However, William
Kempf is giving it another try, so to prove or impossible may prove to be
subtle on epts.
Peter Higley has reviewed early versions of this paper, and made some
good omments to add to its larity. He also attempted a port to the Intel
4.0 ompiler and it fails.
Bjarne Stroustrup's [Str00 omments have inspired us to divide an intermixed paper into two distin t parts. We would also like to thank the boost
readership for their insightful omments, espe ially Dave Abrahams.

Referen es
[Bli99

The Blitz++ library home page, 1999. http://www.oonumeri s.


org/blitz.

[Bon00

V. Bonnard. Corresponden e via the Boost listserver, 2000.

[C++98

International Standard, Programming Languages  C++,


ISO/IEC:14882, 1998.

[CE00

K. Czarne ki and U. Eisene ker. Generative Programming :


Methods, Tools, and Appli ations. Addison-Wesley, 2000.

[Fa 00

The FACT! library home page, 2000. http://www.fz-jueli h.


de/zam/FACT.

[Gib00

B. Gibbons. A portable "typeof" operator.


nal, November 2000.
52

C/C++ Users Jour-

[HCKS99 S. W. Haney, J. Crotinger, S. Karmesin, and S. Smith. Pete, the


portable expression template engine. Dr. Dobb's Journal, pages
8895, O tober 1999. http://www.a l.lanl.gov/pete.
[Jr99a

J. Jrvi. Tuples and multiple return values in C++. Te hni al


Report 249, Turku Centre for Computer S ien e, Mar h 1999.

[Jr99b

J. Jrvi. ML-style tuple assignment in standard C++  extending the multiple return value formalism. Te hni al Report 267,
Turku Centre for Computer S ien e, Mar h 1999.

[Jr00

J. Jrvi. C++ fun tion obje t binders made easy. In Pro eedings of the Generative and Component-Based Software Engineering'99, volume 1799 of Le ture Notes in Computer S ien e, Au-

gust 2000.
[Jr01

J. Jrvi. Tuple types and multiple return values.

Journal, 2001. To appear.

C/C++ Users

[Mey96

S. Meyers. More Ee tive C++: 35 New Ways to Improve Your


Programs and Designs. Addison-Wesley, 1996.

[Mey99

S. Meyers. Implementing operator->* for smart pointers.


Dobb's Journal, O tober 1999.

[MS00

B. M Namara and Y. Smaragdakis. Fun tional Programming in


C++. In Pro eedings of The 2000 International Conferen e on
Fun tional Programming (ICFP). ACM, 2000. http://www. .
gate h.edu/~yannis/f ++.

[PH00

G. Powell and P. Higley. Expression templates as a repla ement


for simple fun tors. C++ Report, 12(5), May 2000.

[SL94

A. A. Stepanov and M. Lee. The standard template library.


Te hni al Report HPL-94-34(R.1), Hewlett-Pa kard Laboratories, April 1994. http://www.hpl.hp. om/te hreports.

[SS00

J. Striegnitz and S. A. Smith. An expression template aware


lambda fun tion. In First Workshop on C++ Template Programming, Erfurt, Germany, O tober 2000.

[STL00

The SGI Standard Template Library.


Te hnology/STL, 2000.

[Str00

B. Stroustrup. Personal orresponden e via email, 2000.

[Vel95

T. L. Veldhuizen. Expression templates.


June 1995.
53

Dr.

http://www.sgi. om/

C++ Report, 7(5):2631,

Turku Centre for Computer S ien e


Lemminkisenkatu 14
FIN-20520 Turku
Finland

http://www.tu s.abo.

University of Turku
 Department of Mathemati al S ien es

bo Akademi University
 Department of Computer S ien e
 Institute for Advan ed Management Systems Resear h

Turku S hool of E onomi s and Business Administration


 Institute of Information Systems S ien e

S-ar putea să vă placă și