Professional Documents
Culture Documents
Jaakko Jrvi
jaakko.jarvi
s.utu.
Gary Powell
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:
Jaakko Jrvi
jaakko.jarvi
s.utu.
Gary Powell
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:
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
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
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:
int foo(int);
for_ea
h(v.begin(), v.end(), free1 = bind(foo, free1));
Next we'll sort the elements of vp:
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:
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:
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 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:
// referen e is ok:
// pointer is ok:
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).
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
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
stru
t data {
int x;
int foo(int y);
};
ve
tor<data*> v;
stru t data {
// if bit 0x001 is set, set bit 0x100, else set bit 0x010
13
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
ase 1:
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 ;
} 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.
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
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()
)
)
);
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
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.
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.
Part II
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
22
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
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
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.
26
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:
28
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:
The apply fun
tion eventually
alls the target fun
tion, whi
h in our example
is the built-in operator<.
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:
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
Figure 5: The binary fun
tion
ase of the topmost layer of the return type
dedu
tion system.
is used to dedu
e that X equals to int. Y is dedu
ed to
onst int with the
primary template:
6.7.1
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.
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.
Figure 6: The bind fun
tion for the
ase where the target fun
tion is a
pointer to a binary non-member fun
tion.
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.
};
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
and
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.
};
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:
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
Figure 12: The lambda_fun
tor_base spe
ialization for a swit
h statement
with one
ase statement and the default statement.
44
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:
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
Figure 13: One of the lambda_fun
tor_base spe
ializations for ex
eption
handling.
47
// using lambda:
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));
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:
11
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
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
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:
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
[Bon00
[C++98
[CE00
[Fa 00
[Gib00
[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
C/C++ Users
[Mey96
[Mey99
[MS00
[PH00
[SL94
[SS00
[STL00
[Str00
[Vel95
Dr.
http://www.sgi. om/
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