diff --git a/doc/index.html b/doc/index.html index 0b3dce1..5539bae 100644 --- a/doc/index.html +++ b/doc/index.html @@ -12,15 +12,7 @@ is granted, provided this copyright notice appears in all copies, and a notice that the code was modified is included with the copyright notice. -
Table of Contents
Table of Contents
The Boost Lambda Library (BLL in the sequel) is a C++ template library, which implements a form of lambda abstraction for C++. @@ -28,7 +20,7 @@ Any other kind of errors (typos, incorrect code, ...) are real errors, and feedb convenient means to define unnamed function objects for STL algorithms.
A line of code says more than a thousand words; the following line outputs the elements of some STL container - a separated by spaces[1]: + a separated by spaces[1]:
for_each(a.begin(), a.end(), std::cout << _1 << ' ');@@ -38,7 +30,7 @@ Any other kind of errors (typos, incorrect code, ...) are real errors, and feedb called with an element of a as the actual argument. This actual argument is substituted for the placeholder, and the function is evaluated.
The BLL is about letting you define small unnamed function objects, such as the one above, directly on the call site of an STL algorithm. -
The library consists of include files only, so there is no installation procedure. The boost include directory must be on the include path. @@ -59,6 +51,8 @@ Any other kind of errors (typos, incorrect code, ...) are real errors, and feedb lambda/exceptions.hpp gives tools for throwing and catching exceptions within lambda functions (includes lambda.hpp). +
+ lambda/algorithm.hpp allows nested STL algorithm invocations.
All definitions are placed in the namespace boost::lambda. -
In most code examples, we omit the namespace prefixes for names in the std and boost::lambda namespaces. +
The BLL uses templates rather heavily, performing numerous recursive instantiations of the same templates. +This has (at least) three implications: +
+While it is possible to write incredibly complex lambda expressions, it probably isn't a good idea. +Compiling such expressions may end up requiring a lot of memory, and being slow. +
+The types of lambda functors that result from even the simplest lambda expressions are cryptic. +Usually the programmer doesn't need to deal with the type at all, but in the case of an error in a lambda expression, the compiler usually outputs the types of the lambda functors involved. +This can make the error messages very long and difficult to interpret, particularly if the compiler outputs the whole chain of template instantiations. +
+The C++ Standard suggests a template nesting level of 17 to help detect infinite recursion. +Complex lambda templates can easily exceed this limit. +Most compilers allow a greater number of nested templates, but commonly require the limit explicitly increased with a command line argument. +
The Standard Template Library (STL) + [[STL94]], now part of the C++ Standard Library [C++98], is a generic container and algorithm library. Typically STL algorithms operate on container elements via function objects. These function objects are passed as arguments to the algorithms.
Any C++ construct, which can be called with the function call syntax, is a function object. @@ -82,7 +89,7 @@ are assumed to be in effect. In addition, it contains adaptors for creating function objects from pointers to unary and binary functions, as well as from pointers to nullary and unary member functions. It also contains binder templates for creating a unary function object from a binary function object by fixing one of the arguments to a constant value. Some STL implementations contain function composition operations as - extensions to the standard [???]. + extensions to the standard [[SGI02]].
All these tools aim at one goal: to make it possible to specify unnamed functions in a call of an STL algorithm, in other words, to pass code fragments as an argument to a function. However, this goal is attained only partially. @@ -123,7 +130,7 @@ for_each(a.begin(), a.end(), cout << (1 + _1)); lambda expression syntax.
Lambda expression are common in functional programming languages. Their syntax varies between languages (and between different forms of lambda calculus), but the basic form of a lambda expressions is: @@ -171,7 +178,7 @@ lambda x y.x+y We refer to this type of C++ lambda expressions as bind expressions.
A lambda expression defines a C++ function object, hence function application syntax is like calling any other function object: the C++ expression (_1 + _2)(1, 2) corresponds to - (lambda x y.x+y) 1 2[2]. + (lambda x y.x+y) 1 2[2].
A bind expression is in effect a partial function application. In partial function application, some of the arguments of a function are bound to fixed values. @@ -196,7 +203,7 @@ lambda x y.x+y list<int> v(10); for_each(v.begin(), v.end(), _1 = 1); - In this example _1 = 1 creates a lambda function which assigns the value 1 to every element in v.[3] + In this example _1 = 1 creates a lambda function which assigns the value 1 to every element in v.[3]
Next, we create a container of pointers and make them point to the elements in the first container v: @@ -257,7 +264,7 @@ for_each(v.begin(), v.end(), _1 = bind(foo, _1));
bind<int>(foo, _1, _2);A rare case, where the ret<type>(bind(...)) syntax does not work, but - bind<type>(...) does, is explained in ???. + bind<type>(...) does, is explained in Section 5.4.1.
A general restriction for the actual arguments is that they cannot be nonconst rvalues. For example: @@ -380,7 +387,7 @@ Note that the last line creates a 3-ary function, which adds 10 to its The first two arguments are discarded.
In addition two these three placeholder types, there is also a fourth placeholder type freeE_type. -The use of this placeholder is defined in ??? describing exception handling in lambda expressions. +The use of this placeholder is defined in Section 5.8 describing exception handling in lambda expressions.
When an actual argument is supplied for a placeholder, the parameter passing mode is always by reference. This means that any side-effects to the placeholder are reflected to the actual argument. For example: @@ -419,7 +426,7 @@ For example, the following is a valid operator expression:
cout << _1, _2[_3] = _1 && false
However, there are some restrictions that originate from the C++ operator overloading rules, and some special cases. -
+
Some operators cannot be overloaded at all, or their overloading rules prevent them to be overloaded to create lambda functors. These operators are ->., ->, new, new[], delete, delete[] and ?: (the conditional operator).
@@ -504,9 +511,9 @@ For member functions, the number of arguments must be < 8, as the object argu Basically, the bind-argument-list must be a valid argument list for the target function, except that any argument can be replaced with a placeholder, or more generally, with a lambda expression. Note that also the target function can be a lambda expression. -
The result of a bind expression is either a nullary[4], unary, binary or 3-ary function object depending on the use of placeholders in the bind-argument-list (see Section 5.1). +
The result of a bind expression is either a nullary[4], unary, binary or 3-ary function object depending on the use of placeholders in the bind-argument-list (see Section 5.1).
-The return type of the lambda functor created by the bind expression can be given as an explicitly qualified template parameter, as in the following example: +The return type of the lambda functor created by the bind expression can be given as an explicitly specified template parameter, as in the following example:
bind<RET>(target-function, bind-argument-list)@@ -562,7 +569,7 @@ find_if(pointers.begin(), pointers.end(), bind(&A::foo, _1, 1));
Even though the interfaces are the same, there are important semantic differences between using a pointer or a reference as the object argument. The differences stem from the way bind-functions take their parameters, and how the bound parameters are stored within the lambda functor. -The object argument has the same parameter passing and storing mechanism than any other bind argument slot (see ???); it is passed as a const reference and stored as a const copy in the lambda functor. +The object argument has the same parameter passing and storing mechanism than any other bind argument slot (see Section 4.4); it is passed as a const reference and stored as a const copy in the lambda functor. This creates some asymmetry between the lambda functor and the original member function, and between seemingly similar lambda functors. For example:
class A {
@@ -607,7 +614,7 @@ bind(&A::set_j, _1, 1)(a); // a.j == 1
Function objects, that is, class objects which have the function call operator defined, can be used as target functions. In general, BLL cannot deduce the return type of an arbitrary function object. However, there are two ways to give BLL this capability for a certain function object class. -
+
The BLL recognizes the typedef result_type in the function object, and uses that as the return type of the function call operator. For example:
@@ -626,7 +633,7 @@ If the function object defines several function call operators, there is no way If the function call operator is a template, the result type may depend on the template parameters. Hence, the typedef ought to be a template too, which the C++ language does not support.
The second method is slightly more complex, but also more flexible. The steps that need to be taken to make BLL aware of the return type(s) of a function object are: @@ -718,7 +725,7 @@ ret<D>( - (_1 + _2))(a, b); // error ret<D>( - ret<C>(_1 + _2))(a, b); // ok
If you find yourself using ret repeatedly with the same types, it is worth while extending the return type deduction (see Section 5.5). -
+
As stated above, the effect of ret is to prevent the return type deduction to be performed. However, there is an exception. Due to the way the C++ template instantiation works, the compiler is always forced to instantiate the return type deduction templates for zero-argument lambda functors. @@ -737,7 +744,7 @@ ret<int>(bind(f, 1)); // fails as well! The BLL cannot deduce the return types of the above bind calls, as F does not define the typedef result_type. One would expect ret to fix this, but for a nullary lambda functor that results from a bind expression (last line above) this does not work. The return type deduction templates are instantiated, even though it would not be necessary and the result is a compilation error. -
The solution to this is not to use the ret function, but rather specify the return type as an explicitly qualified template parameter in the bind call: +
The solution to this is not to use the ret function, but rather define the return type as an explicitly specified template parameter in the bind call:
bind<int>(f, 1); // ok@@ -895,7 +902,7 @@ By using var to make index a lambda expression, we get the des In sum, var(x) creates a nullary lambda functor, which stores a reference to the variable x. When the lambda functor is invoked, a reference to x is returned. -
+
Sometimes a delayed variable or a constant is repeated several times in a lambda expression. It is possible to predefine and name a delayed variable or constant outside a lambda expression. The templates var_type and constant_type serve for this purpose. @@ -924,7 +931,7 @@ Here is an example of naming a delayed constant: constant_type<char>::type space(constant(' ')); for_each(a.begin(),a.end(), cout << space << _1); -
As described in the section called “Assignment and subscript operators”, assignment and subscripting operators must be defined as member functions. This means, that for expressions of the form x = y or x[y] to be interpreted as lambda expressions, the left-hand operand x must be a lambda expression. @@ -1000,7 +1007,78 @@ For example, case_statement<1>(a), where a is some lambd The switch_statement function is specialized for up to 9 case statements. -
+
+The BLL provides lambda functors that throw and catch exceptions. +Lambda functors for throwing exceptions are created with the unary function throw_exception. +The argument to this function is the exception to be thrown, or a lambda functor which creates the exception to be thrown. +A lambda functor for rethrowing exceptions is created with the nullary rethrow function. +
+Lambda expressions for handling exceptions are somewhat more complex. +The general form of a lambda expression for try catch blocks is as follows: + +
+try_catch( + lambda expression, + catch_exception<type>(lambda expression), + catch_exception<type>(lambda expression), + ... + catch_all(lambda expression) +) ++ +The first lambda expression is the try block. +Each catch_exception defines a catch block where the explicitly specified template argument defines the type of the exception to catch. +The lambda expression within the catch_exception defines the actions to take if the exception is caught. +Note that the resulting exception handlers catch the exceptions as references, i.e., +catch_exception<T>(...) results in the catch block: + +
+catch(T& e) { ... }
+
+
+The last catch block can alternatively be a call to
+catch_exception<type>
+or to
+catch_all, which is the lambda expression equivalent to
+catch(...).
+
++ +The Example 1 demonstrates the use of the BLL exception handling tools. +The first handler catches exceptions of type foo_exception. +Note the use of _1 placeholder in the body of the handler. +
+The second handler shows how to throw exceptions, and demonstrates the use of the exception placeholder _E. +It is a special placeholder, which refers to the caught exception object within the handler body. +Here we are handling an exception of type std::exception, which carries a string explaining the cause of the exception. +This explanation can be queried with the zero-argument member function what; +bind(&std::exception::what, _E) creates the lambda function for making that call. + +Note that _E is not a full-fledged placeholder, but rather a special case of _3. +As a consequence, _E cannot be used outside of an exception handler lambda expression, and _3 cannot be used inside of an exception handler lambda expression. +This kind of illegal use of placeholders is caught by the compiler. +
+Finally, the third handler (catch_all) demonstrates rethrowing exceptions. +
Example 1. Throwing and handling exceptions in lambda expressions.
+for_each(
+ a.begin(), a.end(),
+ try_catch(
+ bind(foo, _1), // foo may throw
+ catch_exception<foo_exception>(
+ cout << constant("Caught foo_exception: ")
+ << "foo was called with argument = " << _1
+ ),
+ catch_exception<std::exception>(
+ cout << constant("Caught std::exception: ")
+ << bind(&std::exception::what, _E),
+ throw_exception(constructor<bar_exception>(_1))
+ ),
+ catch_all(
+ (cout << constant("Unknown"), rethrow())
+ )
+ )
+);
+Operators new and delete can be overloaded, but their return types are fixed. Particularly, the return types cannot be lambda functors. Likewise, we cannot create lambda functors from constructors and destructors directly using bind functions. @@ -1027,21 +1105,166 @@ transform(x.begin(), x.end(), y.begin(), back_inserter(v), bind(constructor<pair<int, int> >(), _1, _2)); -Table 2 lists all the function objects related to creating and destroying objects, showing the expression to create and call the function object, and the effect of evaluating that expression. +Table 2 lists all the function objects related to creating and destroying objects, showing the expression to create and call the function object, and the effect of evaluating that expression. -
Table 2. Construction and destruction related function objects
| Function object call | Wrapped expression |
|---|---|
| constructor<T>()(arg_list) | T(arg_list) |
| destructor()(a) | a.~A(), where a is of type A |
| destructor()(pa) | pa.->A(), where pa is of type A* |
| new_ptr<T>()(arg_list) | new T(arg_list) |
| new_array<T>()(sz) | new T[sz] |
| delete_ptr()(p) | delete p |
| delete_array()(p) | delete p[] |
Table 2. Construction and destruction related function objects.
| Function object call | Wrapped expression |
|---|---|
| constructor<T>()(arg_list) | T(arg_list) |
| destructor()(a) | a.~A(), where a is of type A |
| destructor()(pa) | pa.->A(), where pa is of type A* |
| new_ptr<T>()(arg_list) | new T(arg_list) |
| new_array<T>()(sz) | new T[sz] |
| delete_ptr()(p) | delete p |
| delete_array()(p) | delete p[] |
+When a lambda functor is called, the default behavior is to substitute the actual arguments for the placeholders within all subexpressions. +This section describes the tools to prevent the substitution and evaluation of a subexpression, and explains when these tools should be used. +
+The arguments to a bind expression can be arbitrary lambda expressions, e.g., other bind expressions. +For example: -Additional help and ideas: Jeremy Siek, Peter Higley, Peter Dimov +
+int foo(int); int bar(int); ... +int i; +bind(foo, bind(bar, _1)(i); ++The last line makes the call foo(bar(i)); -
[1] In all code examples names of the std - namespace are not prefixed with std::.
[2] Actually, this is not a valid C++ lambda expression. - The reason for this is explained in Section 4.3.
[3] +Note, that the first argument, the target function, in a bind expression is no exception, and can thus be a bind expression too. +The innermost lambda functor just has to return something that can be used as a target function: another lambda functor, function pointer, pointer to member function etc. +For example, in the following code the innermost lambda functor makes a selection between two functions, and returns a pointer to one of them: + +
+int add(int a, int b) { return a+b; }
+int mul(int a, int b) { return a*b; }
+
+int(*)(int, int) add_or_mul(bool x) {
+ return x ? add : mul;
+}
+
+bool condition; int i; int j;
+...
+bind(bind(&add_or_mul, _1), _2, _3)(condition, i, j);
+
+
+A nested bind expression may occur inadvertently, if the target function is a variable with a type that depends on a template parameter. +Typically the target function could be a formal parameter of a function template. +In such a case, the programmer may not know whether the target function is a lambda functor or not. +
Consider the following function template: + +
+template<class F>
+int nested(const F& f) {
+ int x;
+ ...
+ bind(f, _1)(x);
+ ...
+}
+
+
+Somewhere inside the function the formal parameter
+f is used as a target function in a bind expression.
+In order for thisbind call to be valid, f must be a unary function.
++Suppose the following two calls to nested are made: +
+int foo(int); +int bar(int, int); +nested(&foo); +nested(bind(bar, 1, _1)); ++Both are unary functions, or function objects, with appropriate argument and return types, but the latter will not compile. +In the latter call, the bind expression inside nested will become: +
+bind(bind(bar, 1, _1), _1) ++When this is invoked with x, after substituitions we end up trying to call +
+bar(1, x)(x); ++which is an error. +The call to bar returns int, not a unary function or function object. +
+In the example above, the intent of the bind expression in the nested function is to treat f as an ordinary function object, instead of a lambda functor. +The BLL provides the function template unlambda to express this: a lambda functor wrapped inside unlambda is not a lambda functor anymore, and does not take part into the argument substitution process. +Note that for all other argument types unlambda is an identity operation, except for making non-const objects const. +
+Using unlambda, the nested function is written as: +
+template<class F>
+int nested(const F& f) {
+ int x;
+ ...
+ bind(unlambda(f), _1)(x);
+ ...
+}
+
+
++The protect function is related to unlambda. +It is also used to prevent the argument substitution taking place, but whereas unlambda turns a lambda functor into an ordinary function object for good, protect does this temporarily, for just one evaluation round. +For example: + +
+int x = 1, y = 10; +(_1 + protect(_1 + 2))(x)(y); ++ +The first call substitutes x for the leftmost _1 with x, and results in another lambda functor x + (_1 + 2), which after the call with y becomes x + (y + 2), and thus finally 13. +
+Primary motivation for including protect into the library, was to allow nested STL algorithm invocations (Section 5.11). +
+The BLL defines common STL algorithms as function object classes, instances of which can be used as target functions in bind expressions. +For example, the following code iterates over the elements of a two-dimensional array, and computes their sum. + +
+int a[100][200]; +int sum = 0; + +std::for_each(a, a + 100, + bind(ll::for_each(), _1, _1 + 200, protect(sum += _1))); ++ +The BLL versions of the STL algorithms are structs, which define a function call operator (or several overloaded ones) to call the corresponding standard algorithm. +All these structs are placed in the subnamespace boost::lambda:ll. +The supported algorithms are listed in Table 3. +
+Note that there is no easy way to express an overloaded member function call in a lambda expression. +This limits the usefulness of nested STL algorithms, as for instance the begin function has more than one overloaded definitions in container templates. +In general, something analogous to the pseudo-code below cannot be written: + +
+std::for_each(a.begin(), a.end(), + bind(ll::for_each(), _1.begin(), _1.end(), protect(sum += _1))); ++ +Some aid for common special cases can be provided though. +The BLL defines two helper function object classes, call_begin and call_end, which wrap a call to the begin and, respectively, end functions of a container, and return the const_iterator type of the container. +With these helper templates, the above code becomes: +
+std::for_each(a.begin(), a.end(), + bind(ll::for_each(), + bind(call_begin(), _1), bind(call_end(), _1), + protect(sum += _1))); ++ +
In theory, all overhead of using STL algorithms and lambda functors compared to hand written loops can be optimized away, just as the overhead from standard STL function objects and binders. +Depending on the compiler, this can also be true in practice. +
We have only performed limited performance testing, and +our tests suggest that the BLL does not introduce a loss of performance compared to STL function objects. +Hence, with a reasonable optimizing compiler, one should expect the performance characteristics be comparable to using classic STL. +Moreover, with a great optimizing compiler there may be no performance penalty at all. +Note however, that evaluating a lambda functor consist of a sequence of calls to small functions that are declared inline. +If the compiler fails to actually expand these functions inline, the performance, compared to hand written loops, can suffer. +
[STL94] The Standard Template Library. Hewlett-Packard Laboratories. 1994. +www.hpl.hp.com/techreports +.
[SGI02] The SGI Standard Template Library. 2002. www.sgi.com/tech/stl/.
[Jar99] + +C++ Function Object Binders Made Easy. +. Lecture Notes in Computer Science. Springer. 2000.
[Jar01] The Lambda Library : Lambda Abstraction in C++. Second Workshop on C++ Template Programming, Tampa Bay, OOPSLA'01. 2001. www.oonumerics.org/tmpw01/.
[1] In all code examples names of the std + namespace are not prefixed with std::.
[2] Actually, this is not a valid C++ lambda expression. + The reason for this is explained in Section 4.3.
[3] Strictly taken, the C++ standard defines for_each as a non-modifying sequence operation, and the function object passed to for_each should not modify its argument. The requirements for the arguments of for_each are unnecessary strict, since as long as the iterators are mutable, for_each accepts a function object that can have side-effects on their argument. Nevertheless, it is straightforward to provide another function template with the functionality ofstd::for_each but more fine-grained requirements for its arguments. -
[4] A zero-argument function.