Tải bản đầy đủ (.pdf) (6 trang)

O''''Reilly Network For Information About''''s Book part 86 pot

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (21.99 KB, 6 trang )

vec.push_back(2);
vec.push_back(1);
add_prev<int> ap;
std::transform(
vec.begin(),
vec.end(),
vec.begin(),
bind(var(ap),_1));
}
The problem is the call to transform.
std::transform(vec.begin(),vec.end(),vec.begin(),bind(var(ap),_1));
When the binder is instantiated, the mechanism for return type deduction kicks
in…and fails. Thus, the program does not compile, and you must explicitly tell
bind the return type, like so:
std::transform(vec.begin(),vec.end(),vec.begin(),
bind<int>(var(ap),_1));
This is a shorthand notation for the general form of explicitly setting the return
types for lambda expression, and it's the equivalent of this code.
std::transform(vec.begin(),vec.end(),vec.begin(),
ret<int>(bind<int>(var(ap),_1)));
This problem isn't new; it's virtually the same that applies for function objects used
by the Standard Library algorithms. There, the solution is to add typedefs that
state the return type and argument type(s) of the function objects. The Standard
Library even provides helper classes for accomplishing this, through the class
templates unary_function and binary_functionour example class
add_prev could become a compliant function object by either defining the
required typedefs (argument_type and result_type for unary function
objects, first_argument_type, second_argument_type, and
result_type for binary function objects), or inheriting from
unary_function/binary_function.
template <typename T> class add_prev : public std::unary_function<T,T>


Is this good enough for lambda expressions, too? Can we simply reuse this scheme,
and thus our existing function objects, too? Alas, the answer is no. There is a
problem with this typedef approach: What happens when the result type or the
argument type(s) is dependent on a template parameter to a parameterized function
call operator? Or, when there are several overloaded function call operators? Had
there been language support for template typedefs, much of the problem would
be solved, but currently, that's not the case. That's why Boost.Lambda requires a
different approach, through a nested parameterized class called sig. To enable the
return type deduction to work with add_prev, we must define a nested type sig
like this:
template <typename T> class add_prev :
public std::unary_function<T,T> {
T prev_;
public:
template <typename Args> class sig {
public:
typedef T type;
};
// Rest of definition
The template parameter Args is actually a tuple containing the function object
(first element) and the types of the arguments to the function call operator. In our
case, we have no need for this information, as the return type and the argument
type are always T. Using this improved version of add_prev, there's no need to
short-circuit the return type deduction in a lambda expression, so our original
version of the code now compiles cleanly.
std::transform(vec.begin(),vec.end(),vec.begin(),bind(var(ap),_1));
To see how the tuple in the template parameter to sig works, consider another
function object with two function call operators, one version accepting an int
argument, the other accepting a reference to const std::string. The
problem that we need to solve can be expressed as, "if the second element of the

tuple passed to the sig template is of type int, set the return type to
std::string; if the second element of the tuple passed to the sig template is
of type std::string, set the return type to double." To do this, we'll add
another class template that we can specialize and then use in add_prev::sig.
template <typename T> class sig_helper {};
// The version for the overload on int
template<> class sig_helper<int> {
public:
typedef std::string type;
};
// The version for the overload on std::string
template<> class sig_helper<std::string> {
public:
typedef double type;
};
// The function object
class some_function_object {
template <typename Args> class sig {
typedef typename boost::tuples::element<1,Args>::type
cv_first_argument_type;
typedef typename
boost::remove_cv<cv_first_argument_type>::type
first_argument_type;
public:
// The first argument helps us decide the correct version
typedef typename
sig_helper<first_argument_type>::type type;
};
std::string operator()(int i) const {
std::cout << i << '\n';

return "Hello!";
}
double operator()(const std::string& s) const {
std::cout << s << '\n';
return 3.14159265353;
}
};
There are two important parts to study herefirst, the helper class sig_helper,
which is class parameterized on a type T. This type is either int or
std::string, depending on which of the overloaded versions of the function
call operator is requested. By fully specializing this template, the correct
typedef, type, is defined. The next interesting part is the sig class, where the
first argument type (the second element of the tuple) is retrieved, any const or
volatile qualifiers are removed, and the resulting type is used to instantiate the
correct version of the sig_helper class, which has the correct typedef
type. This is a rather complex (but necessary!) way of defining the return types
for our classes, but most of the time, there's only one version of the function call
operator; and then it's a trivial task to correctly add the nested sig class.
It's important that our function objects work without hassle in lambda expressions,
and defining the nested sig class where it's needed is definitely a good idea; it
helps a lot.
Control Structures in Lambda Expressions
We have seen that powerful lambda expressions can be created with ease, but
many programming problems require that we be able to express conditions, which
we do in C++ using if-then-else, for, while, and so on. There are lambda
versions of all C++ control structures in Boost.Lambda. To use the selection
statements, if and switch, include the files "boost/lambda/if.hpp" and
"boost/lambda/switch.hpp", respectively. For the iteration statements,
while, do, and for, include "boost/lambda/loops.hpp". It's not
possible to overload keywords, so the syntax is slightly different than what you're

used to, but the correlation is obvious. As a first example, we'll see how to create a
simple if-then-else construct in a lambda expression. The form is
if_then_else(condition, then-statements, else-statements). There is also another
syntactic form, which has the form if_(condition)[then-statements].else_[else-
statements].
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/if.hpp"
int main() {
using namespace boost::lambda;
std::vector<std::string> vec;
vec.push_back("Lambda");
vec.push_back("expressions");
vec.push_back("really");
vec.push_back("rock");
std::for_each(vec.begin(),vec.end(),if_then_else(
bind(&std::string::size,_1)<=6u,
std::cout << _1 << '\n',
std::cout << constant("Skip.\n")));
std::for_each(vec.begin(),vec.end(),
if_(bind(&std::string::size,_1)<=6u) [
std::cout << _1 << '\n'
]
.else_[
std::cout << constant("Skip.\n")
] );

}
If you've read the whole chapter up to now, you probably find the preceding
example quite readable; if you're jumping right in, this is probably a scary read.
Control structures immediately add to the complexity of reading lambda
expressions, so it does take a little longer to get used to. After you get the hang of
it, it comes naturally (the same goes for writing them!). Deciding which syntactic
form to use is merely a matter of taste; they do exactly the same thing.
In the preceding example, we have a vector of strings and, if their size is less
than or equal to 6, they are printed to std::cout; otherwise, the string "Skip"
is printed. There are a few things worth noting in the if_then_else expression.
if_then_else(
bind(&std::string::size,_1)<=6u,
std::cout << _1 << '\n',
std::cout << constant("Skip.\n")));
First, the condition is a predicate, and it must be a lambda expression! Second, the
then-statement must be a lambda expression! Third, the else-statement must beget
readya lambda expression! The first two come naturally in this case, but it's easy to
forget the constant to make the string literal ("Skip\n") a lambda expression.
The observant reader notices that the example uses 6u, and not simply 6, to make
sure that the comparison is performed using two unsigned types. The reason for
this is that we're dealing with deeply nested templates, which means that when a
lambda expression like this happens to trigger a compiler warning, the output is
really, really long-winded. Try removing the u in the example and see how your
compiler likes it! You should see a warning about comparing signed and unsigned
types because std::string::size returns an unsigned type.
The return type of the control structures is void, with the exception of

×