-
Notifications
You must be signed in to change notification settings - Fork 236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow methods returning IIterable<T> to be used as synchronous generators #361
base: master
Are you sure you want to change the base?
Allow methods returning IIterable<T> to be used as synchronous generators #361
Conversation
|
Yes, but C# already violates that expectation with its own generator methods. |
That seems... bad. But I'll defer to @jonwis on this one |
I realize that this problem is made worse by the fact that APIs tend to accept an |
Agree on this; But, it would be a very sharp edge if the |
My mistake, the C# iterator methods do support being iterated multiple times, as long as they return We could constrain this implementation to nit: |
`wil::make_iterable_from_iterator` to create an `IIterable<T>` helper object C++ coroutines can only be evaluated once, which doesn't fulfill `IIterable<T>`'s requirement that it can return multiple independent iterators. Thus the generator implementation is constrained to `IIterator<T>`, which may only be evaluated once, and a helper method is added that returns an `IIterable<T>` which creates a new generator instance every time an iterator is requested.
I've now changed the implementation so that the generator is constrained onto IIterator<winrt::hstring> Generator(int x)
{
co_yield winrt::to_hstring(x);
}
void Foo()
{
IIterable<winrt::hstring> iterable = wil::make_iterable_from_iterator(&Generator, 3);
} |
template<typename Func, typename... Args, typename TResult = typename details::iterator_result<std::invoke_result_t<Func, Args...>>::type> | ||
winrt::Windows::Foundation::Collections::IIterable<TResult> make_iterable_from_iterator(Func&& func, Args&&... args) | ||
{ | ||
return winrt::make<details::iterable_iterator_helper<TResult, Func, Args...>>(std::forward<Func>(func), std::forward<Args>(args)...); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest adding a way to keep a strong or weak reference to a class here, as well as a pointer. Maybe something similar to C++/WinRT's delegate implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Func
accepts those arguments, it's already possible (e.g. calling a member function with a strong reference that is stored in the IIterable<T>
helper with wil::make_iterable_from_iterator(&Foo::Bar, get_strong())
. That doesn't work with weak references, but what would the semantics be if the weak pointer's object has been destroyed? Should First
return an empty iterator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A member function pointer won't take a strong reference as argument directly via std::invoke/std::apply.
Good point reguarding weak references, lets just ignore that for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah looks like std::invoke will try (*arg).(*f)()
, I did not know that. Nothing to do here, then.
Mostly looks good to me, however it's been far too long since I've dealt with coroutines in C++. @oldnewthing is probably the most resident coroutine expert if he has any input |
I think this is solving the problem at the wrong level. I think we should add a basic |
Why not use std::generator then |
Because it requires C++23, not C++17/20 like the coroutine infrastructure, and has yet to be implemented by the STL. It also does not provide a way to yield multiple items in sequence without suspending, which I have taken advantage of so that GetMany() produces the requested items without unnecessarily suspending the coroutine just to immediately resume it again. (i have benchmarked this and it improved execution times.) |
Given the one approval, should I still follow the suggestion of adding |
I'm not sure what the use case is for returning |
Either that or implement a WinRT interface which returns IIterable via a generator |
The use cases for this feature are the same ones as in C# - as Just like in C#, this can be used any time someone wants to use a generator to implement a collection; right now, the choices are implementing a custom iterator or prefilling and returning a collection of which all elements may not even bee needed, both of which aren't as ergonomic as generators. |
Yes, I understand how this could be used in theory. But are there practical examples of cases in which you want to pass a generator to a Windows Runtime method? I'm hoping to see a compelling scenario like struct watcher_desires
{
bool want_adds;
bool want_removes;
bool want_updates;
};
auto trigger = DeviceWatcher::GetBackgroundTrigger(make_iterable_from_generator(
[=](auto desires) -> IIterator<DeviceWatcherEventKind> {
if (desires.want_adds) co_yield DeviceWatcherEventKind::Add;
if (desires.want_removes) co_yield DeviceWatcherEventKind::Remove;
if (desires.want_updates) co_yield DeviceWatcherEventKind::Update;
}, desires)); |
See #349.
This PR adds a synchronous generator implementation for methods that return
IIterable<T>
.The generator promise implements both
IIterator<T>
andIIterable<T>
and therefore saves an allocation; however, this can be observed viaQueryInterface
, so I'm not too sure whether this is a good idea in practice.