1[/ 2 Copyright Oliver Kowalke 2009. 3 Distributed under the Boost Software License, Version 1.0. 4 (See accompanying file LICENSE_1_0.txt or copy at 5 http://www.boost.org/LICENSE_1_0.txt 6] 7 8[section:motivation Motivation] 9 10In order to support a broad range of execution control behaviour the coroutine 11types of __scoro__ and __acoro__ can be used to ['escape-and-reenter] loops, to 12['escape-and-reenter] recursive computations and for ['cooperative] multitasking 13helping to solve problems in a much simpler and more elegant way than with only 14a single flow of control. 15 16 17[heading event-driven model] 18 19The event-driven model is a programming paradigm where the flow of a program is 20determined by events. The events are generated by multiple independent sources 21and an event-dispatcher, waiting on all external sources, triggers callback 22functions (event-handlers) whenever one of those events is detected (event-loop). 23The application is divided into event selection (detection) and event handling. 24 25[$../../../../libs/coroutine/doc/images/event_model.png [align center]] 26 27The resulting applications are highly scalable, flexible, have high 28responsiveness and the components are loosely coupled. This makes the event-driven 29model suitable for user interface applications, rule-based productions systems 30or applications dealing with asynchronous I/O (for instance network servers). 31 32 33[heading event-based asynchronous paradigm] 34 35A classic synchronous console program issues an I/O request (e.g. for user 36input or filesystem data) and blocks until the request is complete. 37 38In contrast, an asynchronous I/O function initiates the physical operation but 39immediately returns to its caller, even though the operation is not yet 40complete. A program written to leverage this functionality does not block: it 41can proceed with other work (including other I/O requests in parallel) while 42the original operation is still pending. When the operation completes, the 43program is notified. Because asynchronous applications spend less overall time 44waiting for operations, they can outperform synchronous programs. 45 46Events are one of the paradigms for asynchronous execution, but 47not all asynchronous systems use events. 48Although asynchronous programming can be done using threads, they come with 49their own costs: 50 51* hard to program (traps for the unwary) 52* memory requirements are high 53* large overhead with creation and maintenance of thread state 54* expensive context switching between threads 55 56The event-based asynchronous model avoids those issues: 57 58* simpler because of the single stream of instructions 59* much less expensive context switches 60 61The downside of this paradigm consists in a sub-optimal program 62structure. An event-driven program is required to split its code into 63multiple small callback functions, i.e. the code is organized in a sequence of 64small steps that execute intermittently. An algorithm that would usually be expressed 65as a hierarchy of functions and loops must be transformed into callbacks. The 66complete state has to be stored into a data structure while the control flow 67returns to the event-loop. 68As a consequence, event-driven applications are often tedious and confusing to 69write. Each callback introduces a new scope, error callback etc. The 70sequential nature of the algorithm is split into multiple callstacks, 71making the application hard to debug. Exception handlers are restricted to 72local handlers: it is impossible to wrap a sequence of events into a single 73try-catch block. 74The use of local variables, while/for loops, recursions etc. together with the 75event-loop is not possible. The code becomes less expressive. 76 77In the past, code using asio's ['asynchronous operations] was convoluted by 78callback functions. 79 80 class session 81 { 82 public: 83 session(boost::asio::io_service& io_service) : 84 socket_(io_service) // construct a TCP-socket from io_service 85 {} 86 87 tcp::socket& socket(){ 88 return socket_; 89 } 90 91 void start(){ 92 // initiate asynchronous read; handle_read() is callback-function 93 socket_.async_read_some(boost::asio::buffer(data_,max_length), 94 boost::bind(&session::handle_read,this, 95 boost::asio::placeholders::error, 96 boost::asio::placeholders::bytes_transferred)); 97 } 98 99 private: 100 void handle_read(const boost::system::error_code& error, 101 size_t bytes_transferred){ 102 if (!error) 103 // initiate asynchronous write; handle_write() is callback-function 104 boost::asio::async_write(socket_, 105 boost::asio::buffer(data_,bytes_transferred), 106 boost::bind(&session::handle_write,this, 107 boost::asio::placeholders::error)); 108 else 109 delete this; 110 } 111 112 void handle_write(const boost::system::error_code& error){ 113 if (!error) 114 // initiate asynchronous read; handle_read() is callback-function 115 socket_.async_read_some(boost::asio::buffer(data_,max_length), 116 boost::bind(&session::handle_read,this, 117 boost::asio::placeholders::error, 118 boost::asio::placeholders::bytes_transferred)); 119 else 120 delete this; 121 } 122 123 boost::asio::ip::tcp::socket socket_; 124 enum { max_length=1024 }; 125 char data_[max_length]; 126 }; 127 128In this example, a simple echo server, the logic is split into three member 129functions - local state (such as data buffer) is moved to member variables. 130 131__boost_asio__ provides with its new ['asynchronous result] feature a new 132framework combining event-driven model and coroutines, hiding the complexity 133of event-driven programming and permitting the style of classic sequential code. 134The application is not required to pass callback functions to asynchronous 135operations and local state is kept as local variables. Therefore the code 136is much easier to read and understand. 137[footnote Christopher Kohlhoff, 138[@ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3964.pdf 139N3964 - Library Foundations for Asynchronous Operations, Revision 1]]. 140__yield_context__ internally uses __boost_coroutine__: 141 142 void session(boost::asio::io_service& io_service){ 143 // construct TCP-socket from io_service 144 boost::asio::ip::tcp::socket socket(io_service); 145 146 try{ 147 for(;;){ 148 // local data-buffer 149 char data[max_length]; 150 151 boost::system::error_code ec; 152 153 // read asynchronous data from socket 154 // execution context will be suspended until 155 // some bytes are read from socket 156 std::size_t length=socket.async_read_some( 157 boost::asio::buffer(data), 158 boost::asio::yield[ec]); 159 if (ec==boost::asio::error::eof) 160 break; //connection closed cleanly by peer 161 else if(ec) 162 throw boost::system::system_error(ec); //some other error 163 164 // write some bytes asynchronously 165 boost::asio::async_write( 166 socket, 167 boost::asio::buffer(data,length), 168 boost::asio::yield[ec]); 169 if (ec==boost::asio::error::eof) 170 break; //connection closed cleanly by peer 171 else if(ec) 172 throw boost::system::system_error(ec); //some other error 173 } 174 } catch(std::exception const& e){ 175 std::cerr<<"Exception: "<<e.what()<<"\n"; 176 } 177 } 178 179In contrast to the previous example this one gives the impression of sequential 180code and local data (['data]) while using asynchronous operations 181(['async_read()], ['async_write()]). The algorithm is implemented in one 182function and error handling is done by one try-catch block. 183 184[heading recursive SAX parsing] 185To someone who knows SAX, the phrase "recursive SAX parsing" might sound 186nonsensical. You get callbacks from SAX; you have to manage the element stack 187yourself. If you want recursive XML processing, you must first read the entire 188DOM into memory, then walk the tree. 189 190But coroutines let you invert the flow of control so you can ask for SAX 191events. Once you can do that, you can process them recursively. 192 193 // Represent a subset of interesting SAX events 194 struct BaseEvent{ 195 BaseEvent(const BaseEvent&)=delete; 196 BaseEvent& operator=(const BaseEvent&)=delete; 197 }; 198 199 // End of document or element 200 struct CloseEvent: public BaseEvent{ 201 // CloseEvent binds (without copying) the TagType reference. 202 CloseEvent(const xml::sax::Parser::TagType& name): 203 mName(name) 204 {} 205 206 const xml::sax::Parser::TagType& mName; 207 }; 208 209 // Start of document or element 210 struct OpenEvent: public CloseEvent{ 211 // In addition to CloseEvent's TagType, OpenEvent binds AttributeIterator. 212 OpenEvent(const xml::sax::Parser::TagType& name, 213 xml::sax::AttributeIterator& attrs): 214 CloseEvent(name), 215 mAttrs(attrs) 216 {} 217 218 xml::sax::AttributeIterator& mAttrs; 219 }; 220 221 // text within an element 222 struct TextEvent: public BaseEvent{ 223 // TextEvent binds the CharIterator. 224 TextEvent(xml::sax::CharIterator& text): 225 mText(text) 226 {} 227 228 xml::sax::CharIterator& mText; 229 }; 230 231 // The parsing coroutine instantiates BaseEvent subclass instances and 232 // successively shows them to the main program. It passes a reference so we 233 // don't slice the BaseEvent subclass. 234 typedef boost::coroutines::asymmetric_coroutine<const BaseEvent&> coro_t; 235 236 void parser(coro_t::push_type& sink,std::istream& in){ 237 xml::sax::Parser xparser; 238 // startDocument() will send OpenEvent 239 xparser.startDocument([&sink](const xml::sax::Parser::TagType& name, 240 xml::sax::AttributeIterator& attrs) 241 { 242 sink(OpenEvent(name,attrs)); 243 }); 244 // startTag() will likewise send OpenEvent 245 xparser.startTag([&sink](const xml::sax::Parser::TagType& name, 246 xml::sax::AttributeIterator& attrs) 247 { 248 sink(OpenEvent(name,attrs)); 249 }); 250 // endTag() will send CloseEvent 251 xparser.endTag([&sink](const xml::sax::Parser::TagType& name) 252 { 253 sink(CloseEvent(name)); 254 }); 255 // endDocument() will likewise send CloseEvent 256 xparser.endDocument([&sink](const xml::sax::Parser::TagType& name) 257 { 258 sink(CloseEvent(name)); 259 }); 260 // characters() will send TextEvent 261 xparser.characters([&sink](xml::sax::CharIterator& text) 262 { 263 sink(TextEvent(text)); 264 }); 265 try 266 { 267 // parse the document, firing all the above 268 xparser.parse(in); 269 } 270 catch (xml::Exception e) 271 { 272 // xml::sax::Parser throws xml::Exception. Helpfully translate the 273 // name and provide it as the what() string. 274 throw std::runtime_error(exception_name(e)); 275 } 276 } 277 278 // Recursively traverse the incoming XML document on the fly, pulling 279 // BaseEvent& references from 'events'. 280 // 'indent' illustrates the level of recursion. 281 // Each time we're called, we've just retrieved an OpenEvent from 'events'; 282 // accept that as a param. 283 // Return the CloseEvent that ends this element. 284 const CloseEvent& process(coro_t::pull_type& events,const OpenEvent& context, 285 const std::string& indent=""){ 286 // Capture OpenEvent's tag name: as soon as we advance the parser, the 287 // TagType& reference bound in this OpenEvent will be invalidated. 288 xml::sax::Parser::TagType tagName = context.mName; 289 // Since the OpenEvent is still the current value from 'events', pass 290 // control back to 'events' until the next event. Of course, each time we 291 // come back we must check for the end of the results stream. 292 while(events()){ 293 // Another event is pending; retrieve it. 294 const BaseEvent& event=events.get(); 295 const OpenEvent* oe; 296 const CloseEvent* ce; 297 const TextEvent* te; 298 if((oe=dynamic_cast<const OpenEvent*>(&event))){ 299 // When we see OpenEvent, recursively process it. 300 process(events,*oe,indent+" "); 301 } 302 else if((ce=dynamic_cast<const CloseEvent*>(&event))){ 303 // When we see CloseEvent, validate its tag name and then return 304 // it. (This assert is really a check on xml::sax::Parser, since 305 // it already validates matching open/close tags.) 306 assert(ce->mName == tagName); 307 return *ce; 308 } 309 else if((te=dynamic_cast<const TextEvent*>(&event))){ 310 // When we see TextEvent, just report its text, along with 311 // indentation indicating recursion level. 312 std::cout<<indent<<"text: '"<<te->mText.getText()<<"'\n"; 313 } 314 } 315 } 316 317 // pretend we have an XML file of arbitrary size 318 std::istringstream in(doc); 319 try 320 { 321 coro_t::pull_type events(std::bind(parser,_1,std::ref(in))); 322 // We fully expect at least ONE event. 323 assert(events); 324 // This dynamic_cast<&> is itself an assertion that the first event is an 325 // OpenEvent. 326 const OpenEvent& context=dynamic_cast<const OpenEvent&>(events.get()); 327 process(events, context); 328 } 329 catch (std::exception& e) 330 { 331 std::cout << "Parsing error: " << e.what() << '\n'; 332 } 333 334This problem does not map at all well to communicating between independent 335threads. It makes no sense for either side to proceed independently of the 336other. You want them to pass control back and forth. 337 338The solution involves a small polymorphic class event hierarchy, to which 339we're passing references. The actual instances are temporaries on the 340coroutine's stack; the coroutine passes each reference in turn to the main 341logic. Copying them as base-class values would slice them. 342 343If we were trying to let the SAX parser proceed independently of the consuming 344logic, one could imagine allocating event-subclass instances on the heap, 345passing them along on a thread-safe queue of pointers. But that doesn't work 346either, because these event classes bind references passed by the SAX parser. 347The moment the parser moves on, those references become invalid. 348 349Instead of binding a ['TagType&] reference, we could store a copy of 350the ['TagType] in ['CloseEvent]. But that doesn't solve the whole 351problem. For attributes, we get an ['AttributeIterator&]; for text we get 352a ['CharIterator&]. Storing a copy of those iterators is pointless: once 353the parser moves on, those iterators are invalidated. You must process the 354attribute iterator (or character iterator) during the SAX callback for that 355event. 356 357Naturally we could retrieve and store a copy of every attribute and its value; 358we could store a copy of every chunk of text. That would effectively be all 359the text in the document -- a heavy price to pay, if the reason we're using 360SAX is concern about fitting the entire DOM into memory. 361 362There's yet another advantage to using coroutines. This SAX parser throws an 363exception when parsing fails. With a coroutine implementation, you need only 364wrap the calling code in try/catch. 365 366With communicating threads, you would have to arrange to catch the exception 367and pass along the exception pointer on the same queue you're using to deliver 368the other events. You would then have to rethrow the exception to unwind the 369recursive document processing. 370 371The coroutine solution maps very naturally to the problem space. 372 373 374[heading 'same fringe' problem] 375 376The advantages of suspending at an arbitrary call depth can be seen 377particularly clearly with the use of a recursive function, such as traversal 378of trees. 379If traversing two different trees in the same deterministic order produces the 380same list of leaf nodes, then both trees have the same fringe. 381 382[$../../../../libs/coroutine/doc/images/same_fringe.png [align center]] 383 384Both trees in the picture have the same fringe even though the structure of the 385trees is different. 386 387The same fringe problem could be solved using coroutines by iterating over the 388leaf nodes and comparing this sequence via ['std::equal()]. The range of data 389values is generated by function ['traverse()] which recursively traverses the 390tree and passes each node's data value to its __push_coro__. 391__push_coro__ suspends the recursive computation and transfers the data value to 392the main execution context. 393__pull_coro_it__, created from __pull_coro__, steps over those data values and 394delivers them to ['std::equal()] for comparison. Each increment of 395__pull_coro_it__ resumes ['traverse()]. Upon return from 396['iterator::operator++()], either a new data value is available, or tree 397traversal is finished (iterator is invalidated). 398 399In effect, the coroutine iterator presents a flattened view of the recursive 400data structure. 401 402 struct node{ 403 typedef boost::shared_ptr<node> ptr_t; 404 405 // Each tree node has an optional left subtree, 406 // an optional right subtree and a value of its own. 407 // The value is considered to be between the left 408 // subtree and the right. 409 ptr_t left,right; 410 std::string value; 411 412 // construct leaf 413 node(const std::string& v): 414 left(),right(),value(v) 415 {} 416 // construct nonleaf 417 node(ptr_t l,const std::string& v,ptr_t r): 418 left(l),right(r),value(v) 419 {} 420 421 static ptr_t create(const std::string& v){ 422 return ptr_t(new node(v)); 423 } 424 425 static ptr_t create(ptr_t l,const std::string& v,ptr_t r){ 426 return ptr_t(new node(l,v,r)); 427 } 428 }; 429 430 node::ptr_t create_left_tree_from(const std::string& root){ 431 /* -------- 432 root 433 / \ 434 b e 435 / \ 436 a c 437 -------- */ 438 return node::create( 439 node::create( 440 node::create("a"), 441 "b", 442 node::create("c")), 443 root, 444 node::create("e")); 445 } 446 447 node::ptr_t create_right_tree_from(const std::string& root){ 448 /* -------- 449 root 450 / \ 451 a d 452 / \ 453 c e 454 -------- */ 455 return node::create( 456 node::create("a"), 457 root, 458 node::create( 459 node::create("c"), 460 "d", 461 node::create("e"))); 462 } 463 464 // recursively walk the tree, delivering values in order 465 void traverse(node::ptr_t n, 466 boost::coroutines::asymmetric_coroutine<std::string>::push_type& out){ 467 if(n->left) traverse(n->left,out); 468 out(n->value); 469 if(n->right) traverse(n->right,out); 470 } 471 472 // evaluation 473 { 474 node::ptr_t left_d(create_left_tree_from("d")); 475 boost::coroutines::asymmetric_coroutine<std::string>::pull_type left_d_reader( 476 [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){ 477 traverse(left_d,out); 478 }); 479 480 node::ptr_t right_b(create_right_tree_from("b")); 481 boost::coroutines::asymmetric_coroutine<std::string>::pull_type right_b_reader( 482 [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){ 483 traverse(right_b,out); 484 }); 485 486 std::cout << "left tree from d == right tree from b? " 487 << std::boolalpha 488 << std::equal(boost::begin(left_d_reader), 489 boost::end(left_d_reader), 490 boost::begin(right_b_reader)) 491 << std::endl; 492 } 493 { 494 node::ptr_t left_d(create_left_tree_from("d")); 495 boost::coroutines::asymmetric_coroutine<std::string>::pull_type left_d_reader( 496 [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){ 497 traverse(left_d,out); 498 }); 499 500 node::ptr_t right_x(create_right_tree_from("x")); 501 boost::coroutines::asymmetric_coroutine<std::string>::pull_type right_x_reader( 502 [&]( boost::coroutines::asymmetric_coroutine<std::string>::push_type & out){ 503 traverse(right_x,out); 504 }); 505 506 std::cout << "left tree from d == right tree from x? " 507 << std::boolalpha 508 << std::equal(boost::begin(left_d_reader), 509 boost::end(left_d_reader), 510 boost::begin(right_x_reader)) 511 << std::endl; 512 } 513 std::cout << "Done" << std::endl; 514 515 output: 516 left tree from d == right tree from b? true 517 left tree from d == right tree from x? false 518 Done 519 520 521[heading merging two sorted arrays] 522 523This example demonstrates how symmetric coroutines merge two sorted arrays. 524 525 std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b){ 526 std::vector<int> c; 527 std::size_t idx_a=0,idx_b=0; 528 boost::coroutines::symmetric_coroutine<void>::call_type *other_a=0,*other_b=0; 529 530 boost::coroutines::symmetric_coroutine<void>::call_type coro_a( 531 [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){ 532 while(idx_a<a.size()){ 533 if(b[idx_b]<a[idx_a]) // test if element in array b is less than in array a 534 yield(*other_b); // yield to coroutine coro_b 535 c.push_back(a[idx_a++]); // add element to final array 536 } 537 // add remaining elements of array b 538 while(idx_b<b.size()) 539 c.push_back(b[idx_b++]); 540 }); 541 542 boost::coroutines::symmetric_coroutine<void>::call_type coro_b( 543 [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){ 544 while(idx_b<b.size()){ 545 if(a[idx_a]<b[idx_b]) // test if element in array a is less than in array b 546 yield(*other_a); // yield to coroutine coro_a 547 c.push_back(b[idx_b++]); // add element to final array 548 } 549 // add remaining elements of array a 550 while(idx_a<a.size()) 551 c.push_back(a[idx_a++]); 552 }); 553 554 555 other_a=&coro_a; 556 other_b=&coro_b; 557 558 coro_a(); // enter coroutine-fn of coro_a 559 560 return c; 561 } 562 563 564[heading chaining coroutines] 565 566This code shows how coroutines could be chained. 567 568 typedef boost::coroutines::asymmetric_coroutine<std::string> coro_t; 569 570 // deliver each line of input stream to sink as a separate string 571 void readlines(coro_t::push_type& sink,std::istream& in){ 572 std::string line; 573 while(std::getline(in,line)) 574 sink(line); 575 } 576 577 void tokenize(coro_t::push_type& sink, coro_t::pull_type& source){ 578 // This tokenizer doesn't happen to be stateful: you could reasonably 579 // implement it with a single call to push each new token downstream. But 580 // I've worked with stateful tokenizers, in which the meaning of input 581 // characters depends in part on their position within the input line. 582 BOOST_FOREACH(std::string line,source){ 583 std::string::size_type pos=0; 584 while(pos<line.length()){ 585 if(line[pos]=='"'){ 586 std::string token; 587 ++pos; // skip open quote 588 while(pos<line.length()&&line[pos]!='"') 589 token+=line[pos++]; 590 ++pos; // skip close quote 591 sink(token); // pass token downstream 592 } else if (std::isspace(line[pos])){ 593 ++pos; // outside quotes, ignore whitespace 594 } else if (std::isalpha(line[pos])){ 595 std::string token; 596 while (pos < line.length() && std::isalpha(line[pos])) 597 token += line[pos++]; 598 sink(token); // pass token downstream 599 } else { // punctuation 600 sink(std::string(1,line[pos++])); 601 } 602 } 603 } 604 } 605 606 void only_words(coro_t::push_type& sink,coro_t::pull_type& source){ 607 BOOST_FOREACH(std::string token,source){ 608 if (!token.empty() && std::isalpha(token[0])) 609 sink(token); 610 } 611 } 612 613 void trace(coro_t::push_type& sink, coro_t::pull_type& source){ 614 BOOST_FOREACH(std::string token,source){ 615 std::cout << "trace: '" << token << "'\n"; 616 sink(token); 617 } 618 } 619 620 struct FinalEOL{ 621 ~FinalEOL(){ 622 std::cout << std::endl; 623 } 624 }; 625 626 void layout(coro_t::pull_type& source,int num,int width){ 627 // Finish the last line when we leave by whatever means 628 FinalEOL eol; 629 630 // Pull values from upstream, lay them out 'num' to a line 631 for (;;){ 632 for (int i = 0; i < num; ++i){ 633 // when we exhaust the input, stop 634 if (!source) return; 635 636 std::cout << std::setw(width) << source.get(); 637 // now that we've handled this item, advance to next 638 source(); 639 } 640 // after 'num' items, line break 641 std::cout << std::endl; 642 } 643 } 644 645 // For example purposes, instead of having a separate text file in the 646 // local filesystem, construct an istringstream to read. 647 std::string data( 648 "This is the first line.\n" 649 "This, the second.\n" 650 "The third has \"a phrase\"!\n" 651 ); 652 653 { 654 std::cout << "\nfilter:\n"; 655 std::istringstream infile(data); 656 coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile))); 657 coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader))); 658 coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer))); 659 coro_t::pull_type tracer(boost::bind(trace, _1, boost::ref(filter))); 660 BOOST_FOREACH(std::string token,tracer){ 661 // just iterate, we're already pulling through tracer 662 } 663 } 664 665 { 666 std::cout << "\nlayout() as coroutine::push_type:\n"; 667 std::istringstream infile(data); 668 coro_t::pull_type reader(boost::bind(readlines, _1, boost::ref(infile))); 669 coro_t::pull_type tokenizer(boost::bind(tokenize, _1, boost::ref(reader))); 670 coro_t::pull_type filter(boost::bind(only_words, _1, boost::ref(tokenizer))); 671 coro_t::push_type writer(boost::bind(layout, _1, 5, 15)); 672 BOOST_FOREACH(std::string token,filter){ 673 writer(token); 674 } 675 } 676 677 { 678 std::cout << "\nfiltering output:\n"; 679 std::istringstream infile(data); 680 coro_t::pull_type reader(boost::bind(readlines,_1,boost::ref(infile))); 681 coro_t::pull_type tokenizer(boost::bind(tokenize,_1,boost::ref(reader))); 682 coro_t::push_type writer(boost::bind(layout,_1,5,15)); 683 // Because of the symmetry of the API, we can use any of these 684 // chaining functions in a push_type coroutine chain as well. 685 coro_t::push_type filter(boost::bind(only_words,boost::ref(writer),_1)); 686 BOOST_FOREACH(std::string token,tokenizer){ 687 filter(token); 688 } 689 } 690 691[endsect] 692