1. Trang chủ
  2. » Công Nghệ Thông Tin

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

6 74 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 6
Dung lượng 21,99 KB

Nội dung

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 . 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(),. 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));. 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

Ngày đăng: 07/07/2014, 08:20