I like what I see so far, but I'm missing the coroutine future API. We use Giovanni Deretta's Summer of Code Coroutine prototype in production code, and we rely on his 'future' semantics. My intention was to test Oliver's Coroutine proposal by replacing it into our code, but this is not possible without something equivalent to 'future'. Accordingly, this review is based on the library documentation rather than on actual usage. (One could imagine passing a coroutine object itself as a callback for some other library, then calling yield() to await that callback. There are two obstacles. One is that I know of no way to obtain, within the coroutine-function, a reference to the containing coroutine object. The initial 'self' parameter is not that object; it presents a different API. A more serious problem is that the value returned by yield() is fixed by the coroutine template's Signature parameter. With futures, each future object can return a different type; a given coroutine can instantiate an arbitrary number of future objects with an arbitrary number of types to await an arbitrary sequence of callbacks.)
Please always state in your review, whether you think the library should be accepted as a Boost library!
I vote: conditionally accept. I cannot switch to this library without 'future' support.
Additionally please consider giving feedback on the following general topics:
- What is your evaluation of the design?
Good
- What is your evaluation of the implementation?
Did not review
- What is your evaluation of the documentation?
Generally good; detailed suggestions follow
- What is your evaluation of the potential usefulness of the library?
HIGH. More people need this functionality than realize it. :-)
- Did you try to use the library? With what compiler? Did you have any problems?
I did not
- How much effort did you put into your evaluation? A glance? A quick reading? In-depth study?
I read the documentation at the cited link: http://ok73.ok.funpic.de/boost/libs/coroutine/doc/html/
- Are you knowledgeable about the problem domain?
Yes ======================================================================== Detailed comments on documentation: First example in section "Coroutine": "boost::coro::coroutine< void() > c( make_coroutine() );" This example might benefit from a couple comments explaining its purpose. As the first example in the documentation, I thought it might illustrate a short but complete use case. It's only intended to demonstrate a coroutine object being returned by value from a function; but when I first read it I thought make_coroutine() was part of the library API, like make_shared(). "The coroutine-function, as well as its arguments, if any, are copied into the coroutine's state. If a reference is required, use boost::ref." Excellent! This is an important point. "The maximum number of arguments of coroutine-function is 10." Is that limit adjustable with a #define symbol? If not, it should be. Second example under "Executing a coroutine": coro_t c( boost::bind( fn, _1, 7) ); std::cout << "main() starts coroutine c" << std::endl; // yield was called so we returned ^ This comment, at this point in the code, perplexed me. while ( ! ctx.is_complete() ) ^ 'ctx' undefined, I think you mean coro_t 'c' { std::cout << "main() calls coroutine c" << std::endl; // execution control is transfered to c c(); // yield() was called within fn() } "In contrast to threads, which are preemtive, coroutine switches are cooperative (programmer controls when a switch will happen). The kernel is not involved in the coroutine switches." s/preemtive/preemptive/ I would love it if, somewhere in the introductory material, the library documentation emphasized the pragmatic benefit of cooperative switching: it requires no new defensive locking. s/allways/always/ "The arguments passed to coroutine<>::operator()(), in one coroutine, is returned by coroutine<>::self_t::yield() in the other coroutine..." At first glance this makes the reader wonder how yield() can possibly return up to 10 coroutine arguments. May I suggest: "...is returned (as a boost::tuple) by..." ? Example under "coroutine-function with multiple arguments": "boost::tuple< int, int > ret = self.yield( tmp);" This describes exactly the type returned, so is correct. Even so, I find myself thinking that a dubious reader might be more favorably impressed by an example using a couple scalar variables with boost::tie(). And it might clarify the example to use different types for the coroutine parameters. Example under "Exit a coroutine-function": s/self.yield_return()/self.yield_break()/ "An exception thrown inside coroutine-function (transfered via exception-pointer - see Boost.Exception for details and requirements) will be re-thrown by coroutine<>::operator()()." I'm not familiar with Boost.Exception. Will any exception be re-thrown, including exceptions raised by the runtime? Or does this only apply to exceptions derived from a particular base class, or thrown in a particular way? If there are such limitations, mentioning them briefly here would be extremely useful. "Code executed by coroutine must not prevent the propagation of the forced_unwind exception. Absorbing that exception will cause stack unwinding to fail. Thus, any code that catches all exceptions must rethrow the pending exception." This and the example should clarify that we're talking about code within the coroutine-function. (And is forced_unwind also within the boost::coro namespace?) If forced_unwind is the exception thrown when a (! is_complete()) coroutine object is destroyed, that's worth mentioning. It's possible that a coroutine-function might want to perform cleanup specific to that case. coroutine::self_t reference: "T yield( R);" T and R undefined. "template< typename Fn > coroutine( Fn fn, std::size_t size = ctx::default_stacksize(), flag_unwind_t do_unwind = stack_unwind, bool preserve_fpu = true, Allocator alloc = Allocator() );" flag_unwind_t undefined. "R operator()(A0 a0, ..., A9 a9);" R, A0, ... A9 undefined. Example in section "Generator": Undefined make_generator() function wants a comment. Again, this is a hypothetical user function rather than part of the boost::coro::generator API. "The first argument of generator-function must be of type generator<>::self_t, ..." The "only" argument? I'd like to see some words when generator is first introduced clarifying the ways in which it differs from coroutine. This what I think: * A generator-function accepts no arguments (other than self_t), so yield() always returns void. * yield_break() does not throw an exception in the invoking coroutine. * Anything else? It might be worth a few words to clarify that when you pass a boost::bind() expression, *that* is the generator-function, and it must accept only self_t. Any other parameters bound by boost::bind() are irrelevant to boost::coro::generator. In that connection, the function f() in the first "Executing a generator" example should probably accept a different parameter type than int so the reader can clearly distinguish between the type accepted by f() and the type passed to yield(). And in *that* connection, the self.yield() call in that example should pass an int value. "The generator-function, as well as its arguments, if any, are copied into the generator's state." Um, what arguments? Again, if you're using boost::bind() to adapt a function that accepts anything more than self_t, processing of additional arguments is up to boost::bind() -- not boost::coro::generator. "The maximum number of arguments of generator-function is 10." What does this mean? "generator-function is invoked the first time inside the constructor of generator." That makes no sense to me. Is the value passed to the first yield() call simply discarded? If so, why? "If generator-function can not return valid values anymore generator<>::self_t::yield_break() should be called. This function returns the execution control back to the caller and sets the generator to be complete (is_complete() returns true)." is_complete() is not documented for boost::coro::generator. "Of curse the generator-function can be exited with a return expression." s/curse/course/ Is there any semantic difference between executing 'return' in a generator-function and calling yield_break() in a generator-function? If not, is yield_break() provided only for consistency with coroutine? If a generator-function has a declared return type, is that value discarded? Example in "Exit a generator-function": "self_yield( i);" should be self.yield(i); "self.yield_return();" should be self.yield_break(); class generator::self_t reference: "void yield( R);" Is R the same as Result here? "Result operator()() Preconditions: *this is not a not-a-generator, ! is_complete()." is_complete() is not documented for boost::coro::generator. "void self_t::yield( Result) Effects: Gives execution control back to calling context by returning a value of type Result. The return type of this function is a boost::tuple<> containing the arguments passed to generator<>::operator()()." That entire last sentence seems untrue. "void self_t::yield_break() Effects: Gives execution control back to calling context and sets the generator to be complete. generator<>::operator()() in the other generator throws exception coroutine_terminated." Last sentence above contradicts other generator documentation.
Hello, Am 07.09.2012 23:35, schrieb Nat Linden:
My intention was to test Oliver's Coroutine proposal by replacing it into our code, but this is not possible without something equivalent to 'future'. Accordingly, this review is based on the library documentation rather than on actual usage.(One could imagine passing a coroutine object itself as a callback for some other library, then calling yield() to await that callback. There are two obstacles. One is that I know of no way to obtain, within the coroutine-function, a reference to the containing coroutine object. The initial 'self' parameter is not that object; it presents a different API. A more serious problem is that the value returned by yield() is fixed by the coroutine template's Signature parameter. With futures, each future object can return a different type; a given coroutine can instantiate an arbitrary number of future objects with an arbitrary number of types to await an arbitrary sequence of callbacks.) I decided not to provide the future<> facility becuase I've had some concerns about the 'correctness' of the interface.
My intention is to provide a small, clean interface which is hardly to misuse. In my opinion a coroutine is a language-level construct allowing to enter a routine multiple times (by preserving the local state in the routine) == multi-entry routine. Therefore I want a strong contract between the caller and the coroutine. This contract is established via the signature and the return type of the coroutine (coroutien-fn). Thus the interface provided by boost.coroutine must be stringent in the case that the coroutine can only activated/entered via coroutine<>::operator() - no other way to jump into the coroutines body (coroutine-fn) is possible. Additionally the return value and the coroutine parameter of coroutine<>::operator() must be the same as declared in the signature. The future<> concept in Giovannis library violates this contract. You have additional ways to jump into the coroutine's body. You can pass other parameters and return a different type than the coroutine signature defines. I think this violates the design. With future<> you have to test if the coroutine is waiting (call coroutine<>::wait()) after return from coroutine<>::operator() (context switch) - otherwise you can get garbage in the retur nvalue because the coroutine was left via future<> instead of the usual way which is coroutine::self_t::yield(). I don't know your code but if you need 'future' semantics I suggest boost.fiber (http://ok73.ok.funpic.de/boost/libs/fiber/doc/html/, http://ok73.ok.funpic.de/boost.fiber.zip). boost.fiber provides lightweight threads using boost.context (context switching). The lib provides classes like mutex, condition-variables, event-variables and futures. You can use it like boost.thread - but it provides cooperative multitasking.
"The maximum number of arguments of coroutine-function is 10." it's a limitation of boost.tuple
<snip> I'll update the docu as you suggested.
"An exception thrown inside coroutine-function (transfered via exception-pointer - see Boost.Exception for details and requirements) will be re-thrown by coroutine<>::operator()()."
I'm not familiar with Boost.Exception. Will any exception be re-thrown, including exceptions raised by the runtime? Or does this only apply to exceptions derived from a particular base class, or thrown in a particular way? If there are such limitations, mentioning them briefly here would be extremely useful. I gave the hint that the docu of boost.exception will describe all the requirements. Usally the used should not let an exceptio nescape from a coroutine-fn. The implementation (trampoline function inside) catches with ellipses and assigns it to exception ptr: catch (...) { context->flags_ |= flag_has_exception; context->except_ = current_exception(); } In the worst case you get an exception of type unknown_exception rethrown.
"Code executed by coroutine must not prevent the propagation of the forced_unwind exception. Absorbing that exception will cause stack unwinding to fail. Thus, any code that catches all exceptions must rethrow the pending exception."
This and the example should clarify that we're talking about code within the coroutine-function. OK (And is forced_unwind also within the boost::coro namespace?) boost::coro::detail - not derived from std::exception If forced_unwind is the exception thrown when a (! is_complete()) coroutine object is destroyed, that's worth mentioning. It's possible that a coroutine-function might want to perform cleanup specific to that case. OK Example in section "Generator":
Undefined make_generator() function wants a comment. Again, this is a hypothetical user function rather than part of the boost::coro::generator API.
"The first argument of generator-function must be of type generator<>::self_t, ..."
The "only" argument? You could bind parameters to function entry I'd like to see some words when generator is first introduced clarifying the ways in which it differs from coroutine. This what I think:
* A generator-function accepts no arguments (other than self_t), so yield() always returns void. yes - it is required to return void, must except self_t as first arg and parameters can be bound via boost::bind() * yield_break() does not throw an exception in the invoking coroutine. yield_break() was removed from the generator<>::self_t class (in git repo)
<snip> I'll add your comments to the docu
"generator-function is invoked the first time inside the constructor of generator."
That makes no sense to me. Is the value passed to the first yield() call simply discarded? If so, why? I tried to express that a generator must be tested before you can use it:
gen_t gen(...); if ( gen) { int x = gen(); } in order to know if gen is valid (== it will return a value) the return-value must be fetched from the generator-fn.
"If generator-function can not return valid values anymore generator<>::self_t::yield_break() should be called. This function returns the execution control back to the caller and sets the generator to be complete (is_complete() returns true)."
is_complete() is not documented for boost::coro::generator. yes - you have to use generator<>::unspec_bool() or generator<>::operator!() Is there any semantic difference between executing 'return' in a generator-function and calling yield_break() in a generator-function? If not, is yield_break() provided only for consistency with coroutine? yield_break() is removed now
regards, Oliver
On Sat, Sep 8, 2012 at 3:09 PM, Oliver Kowalke
Am 07.09.2012 23:35, schrieb Nat Linden:
My intention was to test Oliver's Coroutine proposal by replacing it into our code, but this is not possible without something equivalent to 'future'.
I decided not to provide the future<> facility becuase I've had some concerns about the 'correctness' of the interface.
My intention is to provide a small, clean interface which is hardly to misuse. In my opinion a coroutine is a language-level construct allowing to enter a routine multiple times (by preserving the local state in the routine) == multi-entry routine. Therefore I want a strong contract between the caller and the coroutine. This contract is established via the signature and the return type of the coroutine (coroutien-fn). Thus the interface provided by boost.coroutine must be stringent in the case that the coroutine can only activated/entered via coroutine<>::operator() - no other way to jump into the coroutines body (coroutine-fn) is possible. Additionally the return value and the coroutine parameter of coroutine<>::operator() must be the same as declared in the signature.
The future<> concept in Giovannis library violates this contract. You have additional ways to jump into the coroutine's body. You can pass other parameters and return a different type than the coroutine signature defines. I think this violates the design.
This sounds like a classic collision between theory and practice. All I can say is that we heavily rely on the functionality provided by Giovanni's 'future' objects, and I cannot convert to a library that does not provide something equivalent. Our use case, as I said at C++ Now last May, seems fairly straightforward. Giovanni's Coroutine library allows us to write code that invokes asynchronous operations, yet retains the simplicity and maintainability of using blocking operations. Cooperative context switching is essential to us: we cannot use a new thread for this purpose due to the prohibitive cost of discovering and protecting accesses to all shared data objects. I've been using the term 'coroutine' for this tactic because Giovanni does. Perhaps that's an abuse of the theoretical concept of "coroutine;" if so I apologize. But whatever you call it, we need that API, or something very like it.
I don't know your code but if you need 'future' semantics I suggest boost.fiber (http://ok73.ok.funpic.de/boost/libs/fiber/doc/html/, http://ok73.ok.funpic.de/boost.fiber.zip). boost.fiber provides lightweight threads using boost.context (context switching).
Thank you for the links. I will look it over. I'm sorry to say that I've overlooked boost.fiber until now because (a) I don't believe it was announced or discussed on the boost-users mailing list, and I'm not on the boost developers' list; and (b) when I see the word "fiber" in the context of program context, I immediately think "Windows-specific." I will be glad to be wrong in that assumption.
The lib provides classes like mutex, condition-variables, event-variables and futures. You can use it like boost.thread - but it provides cooperative multitasking.
Hmm! Dangerous though it is to shoot off my mouth before even starting to read about boost.fiber, I immediately wonder about the need for mutex in the context of cooperative context switching. Time to stop speculating and start reading. ;-) ======================================================================== What follows are responses to specific documentation comments.
"The maximum number of arguments of coroutine-function is 10."
it's a limitation of boost.tuple
Good. Mentioning that in the documentation's Note would permit an interested party to consult Tuple documentation to discover how to lift the limit if needed.
"An exception thrown inside coroutine-function (transfered via exception-pointer - see Boost.Exception for details and requirements) will be re-thrown by coroutine<>::operator()()."
I gave the hint that the docu of boost.exception will describe all the requirements.
Thank you. You're correct that this permits me to research it myself. Anything more is simply to inform a lazy reader. :-)
Usally the used should not let an exceptio nescape from a coroutine-fn.
That's an interesting remark, and I would like to understand better why you say that. I may well instantiate a coroutine with the assumption that low-level exceptions in (code called by) coroutine-function will be handled by the code that instantiates the coroutine. Am I misguided?
In the worst case you get an exception of type unknown_exception rethrown.
Good. It might be worth asserting in the documentation that -- even though coroutine-function should ideally have a top-level try/catch construct -- the library ensures that an exception in coroutine-function will never be silently swallowed.
"The first argument of generator-function must be of type generator<>::self_t, ..."
The "only" argument?
You could bind parameters to function entry
As I said in my previous note, it seems worth a few words in the Generator documentation to distinguish between the generator-function (possibly a bind() expression using any of several available bind() implementations) and the C++ function bound by that bind() expression. While the C++ function is of primary interest to the coder, and the bind() expression seems a mere detail -- the coroutine library sees it differently. Requirements on the coroutine-function and generator-function apply to the actual expression (possibly a bind() expression) passed to the coroutine or generator constructor. The bound C++ function is effectively invisible to the coroutine library. It is from that perspective that I would clarify that generator-function must accept exactly one argument of type generator<>::self_t.
yield_break() was removed from the generator<>::self_t class (in git repo)
Thank you. I'm glad the coder doesn't have to decide whether to execute 'return' or 'yield_break()'.
"generator-function is invoked the first time inside the constructor of generator."
That makes no sense to me. Is the value passed to the first yield() call simply discarded? If so, why?
I tried to express that a generator must be tested before you can use it:
gen_t gen(...); if ( gen) { int x = gen(); }
in order to know if gen is valid (== it will return a value) the return-value must be fetched from the generator-fn.
Oh! Light belatedly dawns. So a generator object constructed with a generator-function with an empty body would immediately test 'false'. That implies that the generator object buffers the value passed to yield() until operator() is called to retrieve it. That seems worth mentioning. It would have corrected my incomplete mental model.
"If generator-function can not return valid values anymore generator<>::self_t::yield_break() should be called. This function returns the execution control back to the caller and sets the generator to be complete (is_complete() returns true)."
is_complete() is not documented for boost::coro::generator.
yes - you have to use generator<>::unspec_bool() or generator<>::operator!()
So the reference to is_complete() in the paragraph quoted above should be removed or amended.
Hello Nat, Am 10.09.2012 19:12, schrieb Nat Linden:
This sounds like a classic collision between theory and practice. All I can say is that we heavily rely on the functionality provided by Giovanni's 'future' objects, and I cannot convert to a library that does not provide something equivalent.
Our use case, as I said at C++ Now last May, seems fairly straightforward. Giovanni's Coroutine library allows us to write code that invokes asynchronous operations, yet retains the simplicity and maintainability of using blocking operations. Cooperative context switching is essential to us: we cannot use a new thread for this purpose due to the prohibitive cost of discovering and protecting accesses to all shared data objects.
with boost.fiber you don't spawn a new thread. it is another abstraction over context switching /cooperative multitasking. wikipedia: 'Fibers describe essentially the same concept as coroutines. The distinction, if there is any, is that coroutines are a language-level construct, a form of control flow, while fibers are a systems-level construct, viewed as threads that happen not to run concurrently. Priority is contentious; fibers may be viewed as an implementation of coroutines[1], or as a substrate on which to implement coroutines.'
I've been using the term 'coroutine' for this tactic because Giovanni does. Perhaps that's an abuse of the theoretical concept of "coroutine;" if so I apologize. But whatever you call it, we need that API, or something very like it.
I don't know your code but if you need 'future' semantics I suggest boost.fiber (http://ok73.ok.funpic.de/boost/libs/fiber/doc/html/, http://ok73.ok.funpic.de/boost.fiber.zip). boost.fiber provides lightweight threads using boost.context (context switching). Thank you for the links. I will look it over.
I'm sorry to say that I've overlooked boost.fiber until now because (a) I don't believe it was announced or discussed on the boost-users mailing list, and I'm not on the boost developers' list; and (b) when I see the word "fiber" in the context of program context, I immediately think "Windows-specific." I will be glad to be wrong in that assumption.
Previously I called it stratum (stratified) but members of the developer list requested to rename it to fiber
The lib provides classes like mutex, condition-variables, event-variables and futures. You can use it like boost.thread - but it provides cooperative multitasking. Hmm! Dangerous though it is to shoot off my mouth before even starting to read about boost.fiber, I immediately wonder about the need for mutex in the context of cooperative context switching. Time to stop speculating and start reading. ;-)
The mutex in boost.fiber has nothing to do with the mutext of boost.thread (e.g. pthread_mutex etc.) It is an abstraction over context switching. my intention was to write code for cooperative mutlitasking as I would do it for threads.
======================================================================== What follows are responses to specific documentation comments.
"The maximum number of arguments of coroutine-function is 10." it's a limitation of boost.tuple Good. Mentioning that in the documentation's Note would permit an interested party to consult Tuple documentation to discover how to lift the limit if needed.
OK - I add that the limit is raised by boost.tuple
"An exception thrown inside coroutine-function (transfered via exception-pointer - see Boost.Exception for details and requirements) will be re-thrown by coroutine<>::operator()()." I gave the hint that the docu of boost.exception will describe all the requirements. Thank you. You're correct that this permits me to research it myself. Anything more is simply to inform a lazy reader. :-)
some remarks added to new version of boost.coroutine
Usally the used should not let an exceptio nescape from a coroutine-fn. That's an interesting remark, and I would like to understand better why you say that. I may well instantiate a coroutine with the assumption that low-level exceptions in (code called by) coroutine-function will be handled by the code that instantiates the coroutine. Am I misguided?
I suggest that the coroutine-fn handles the exceptions itself - exceptions escaped from coroutine-fn are catched and in the worst case re-thrown as unknown_excpetion
In the worst case you get an exception of type unknown_exception rethrown. Good. It might be worth asserting in the documentation that -- even though coroutine-function should ideally have a top-level try/catch construct -- the library ensures that an exception in coroutine-function will never be silently swallowed. done
"The first argument of generator-function must be of type generator<>::self_t, ..."
The "only" argument? You could bind parameters to function entry As I said in my previous note, it seems worth a few words in the Generator documentation to distinguish between the generator-function (possibly a bind() expression using any of several available bind() implementations) and the C++ function bound by that bind() expression. While the C++ function is of primary interest to the coder, and the bind() expression seems a mere detail -- the coroutine library sees it differently. Requirements on the coroutine-function and generator-function apply to the actual expression (possibly a bind() expression) passed to the coroutine or generator constructor. The bound C++ function is effectively invisible to the coroutine library.
It is from that perspective that I would clarify that generator-function must accept exactly one argument of type generator<>::self_t. I thin I've explained it in a more detail in the new version (at least I hope so)
"generator-function is invoked the first time inside the constructor of generator."
That makes no sense to me. Is the value passed to the first yield() call simply discarded? If so, why? I tried to express that a generator must be tested before you can use it:
gen_t gen(...); if ( gen) { int x = gen(); }
in order to know if gen is valid (== it will return a value) the return-value must be fetched from the generator-fn. Oh! Light belatedly dawns. So a generator object constructed with a generator-function with an empty body would immediately test 'false'. yes - because it will not return a (usefull) value
That implies that the generator object buffers the value passed to yield() until operator() is called to retrieve it. That seems worth mentioning. It would have corrected my incomplete mental model. I've some notes in the docu - maybe not explicit enought?!
"If generator-function can not return valid values anymore generator<>::self_t::yield_break() should be called. This function returns the execution control back to the caller and sets the generator to be complete (is_complete() returns true)."
is_complete() is not documented for boost::coro::generator. yes - you have to use generator<>::unspec_bool() or generator<>::operator!() So the reference to is_complete() in the paragraph quoted above should be removed or amended. done
regards, Oliver
On Mon, Sep 10, 2012 at 1:29 PM, Oliver Kowalke
with boost.fiber you don't spawn a new thread. it is another abstraction over context switching /cooperative multitasking.
wikipedia: ...
Thank you for clarifying. I've only just started looking over boost.fiber documentation, but already it's clear that I don't need Boost.Coroutine to be the same as Boost.Fiber. Does that change my vote for inclusion of Boost.Coroutine? No: I still want it to become a Boost library. I am a huge fan of Python generators, and Boost.Coroutine is already more powerful than those. I intend to look through the Boost.Coroutine examples because I consider those an important part of a library's documentation. For one thing, they can help give a reader a sense of the true power of this kind of construct. Examples I would like to see include: - using a recursive generator to emit iterators over a tree structure (possibly wrapped in a Boost iterator facade) - using a generator for stateful filtering of text lines from a file - composing a chain of such filters - integrating such a filter with Boost.IOStreams - using a coroutine as a stateful sink for text output, e.g. filling output lines with variable-length items within a specified margin.
Previously I called it stratum (stratified) but members of the developer list requested to rename it to fiber
Ah! I did mention your candidate Boost.Stratified library in May. I'm glad to know that we're still talking about the same library.
Examples I would like to see include:
- using a recursive generator to emit iterators over a tree structure (possibly wrapped in a Boost iterator facade) - using a generator for stateful filtering of text lines from a file - composing a chain of such filters - integrating such a filter with Boost.IOStreams - using a coroutine as a stateful sink for text output, e.g. filling output lines with variable-length items within a specified margin.
I could add your suggested examples (some simple tree iterating is already available). But it takes time - I've three little boy and you can image I've never enough time (kids and C++). regards, Oliver
On Tue, Sep 11, 2012 at 2:33 AM, Oliver Kowalke
Examples I would like to see include: ...
I could add your suggested examples (some simple tree iterating is already available). But it takes time - I've three little boy and you can image I've never enough time (kids and C++).
Consider this an invitation to the community at large. My 'yes' vote is not conditional on those examples.
[This is no longer a review comment, simply a follow-up to discussion
arising from review comments.]
On Mon, Sep 10, 2012 at 5:11 PM, Nat Linden
Examples I would like to see include:
- using a recursive generator to emit iterators over a tree structure (possibly wrapped in a Boost iterator facade)
It occurred to me that one could write a generic generator_iterator adapter (somewhat analogous to transform_iterator). This adapter could accept an arbitrary generator, mapping the generator API into the iterator API. Following convention, a default-constructed iterator would represent the end of the sequence. I'd really like for that adapter to be included as part of the Boost.Coroutine library. I'd like to be able to write a generator-function, wrap it in a generator object and wrap that in the adapter to get a forward iterator over the results from that generator-function.
participants (2)
-
Nat Linden
-
Oliver Kowalke