-
Notifications
You must be signed in to change notification settings - Fork 32
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
Interopping with containers in third-party libraries #213
Comments
First of all, I'm glad to hear that this (mostly) works :) I agree that this does require more boilerplate than would be ideal. One thing I'd like to do (and I'm slowly making progress on) is to provide more default implementations when the cursor type (as returned by As it stands, it's worth pointing out that Also, please feel free to submit an issue about |
(although micro-benchmarks are meaningless; profiling is much more useful) just ran benchmarks with and without the static void run1(benchmark::State &state)
{
auto a = v::iota(0) | v::take(2000);
immer::vector<int> v = to_immer_vector(a);
for (auto _ : state) {
auto seq = f::ref(v);
seq.for_each([](const auto &e) { boundary((void *) &e); });
}
}
Mistakenly used
(updated)
environment
(contiguous container is indeed faster in the benchmark. but as of |
(Confirmed that the updated result is stable by re-running it multiple times.) With the updated result, the custom implementation duplicated from contiguous sequence trait is significantly faster. I haven't looked into the default implementation of |
Yeah, |
It seems possible to (ab)use deducing this for static polymorphism in this case: template<typename T>
struct sequence_traits_t
{};
// Default implementation for contiguous, sized ranges
template<typename R>
requires(!flux::detail::derived_from_inline_sequence_base<R> && std::ranges::contiguous_range<R>
&& std::ranges::sized_range<R> && std::ranges::contiguous_range<R const>
&& std::ranges::sized_range<R const>)
struct sequence_traits_t<R>
{
using value_type = std::ranges::range_value_t<R>;
constexpr auto first(this auto &&, auto &) -> flux::index_t { return flux::index_t{0}; }
constexpr auto is_last(this auto &&trait, auto &self, flux::index_t idx)
{
return idx >= trait.size(self);
}
constexpr auto inc(this auto &&trait, auto &self, flux::index_t &idx)
{
FLUX_DEBUG_ASSERT(idx < trait.size(self));
idx = flux::num::checked_add(idx, flux::distance_t{1});
}
constexpr auto read_at(this auto &&trait, auto &self, flux::index_t idx) -> decltype(auto)
{
indexed_bounds_check(idx, trait.size(self));
return trait.data(self)[idx];
}
constexpr auto read_at_unchecked(this auto &&trait, auto &self, flux::index_t idx)
-> decltype(auto)
{
return trait.data(self)[idx];
}
constexpr auto dec(this auto &&, auto &, flux::index_t &idx)
{
FLUX_DEBUG_ASSERT(idx > 0);
idx = flux::num::checked_sub(idx, flux::distance_t{1});
}
constexpr auto last(this auto &&trait, auto &self) -> flux::index_t { return trait.size(self); }
constexpr auto inc(this auto &&trait, auto &self, flux::index_t &idx, flux::distance_t offset)
{
FLUX_DEBUG_ASSERT(flux::num::checked_add(idx, offset) <= trait.size(self));
FLUX_DEBUG_ASSERT(flux::num::checked_add(idx, offset) >= 0);
idx = flux::num::checked_add(idx, offset);
}
constexpr auto distance(this auto &&, auto &, flux::index_t from, flux::index_t to)
-> flux::distance_t
{
return flux::num::checked_sub(to, from);
}
constexpr auto size(this auto &&, auto &self) -> flux::distance_t
{
return checked_cast<flux::distance_t>(std::ranges::ssize(self));
}
constexpr auto data(this auto &&, auto &self) -> auto * { return std::ranges::data(self); }
constexpr auto for_each_while(this auto &&, auto &self, auto &&pred) -> flux::index_t
{
auto iter = std::ranges::begin(self);
auto const end = std::ranges::end(self);
while (iter != end) {
if (!std::invoke(pred, *iter)) {
break;
}
++iter;
}
return checked_cast<flux::index_t>(iter - std::ranges::begin(self));
}
};
template<typename T>
constexpr auto sequence_traits = [] {
static_assert(std::is_empty_v<sequence_traits_t<T>>);
return sequence_traits_t<T>{};
}(); And to customize it: template<typename T>
struct sequence_traits_t<immer::vector<T>> : sequence_traits_t<std::vector<T>>
{
constexpr auto read_at(this auto &&trait, auto &self, flux::index_t idx) -> decltype(auto)
{
indexed_bounds_check(idx, trait.size(self));
return self[idx];
}
constexpr auto read_at_unchecked(this auto &&, auto &self, flux::index_t idx) -> decltype(auto)
{
return self[idx];
}
}; This causes the syntax to change from |
The core mechanism of static polymorphism with deducing this is that member functions (containing call to other member functions) become templates, and are only instantiated when a concrete type is passed: struct Trait
{
auto foo(this auto const &) { std::puts("Trait::foo()"); }
auto bar(this auto const &self) { self.foo(); }
};
struct MyTrait : Trait
{
auto foo(this auto const &) { std::puts("MyTrait::foo()"); }
};
int main()
{
MyTrait t{};
t.bar();
}
|
I attempt to use flux with immer::vector (I know, that's a weird thing to do. immer iterators are already const and never invalidates). For the most part (except for
.to<immer::vector<T>>()
) it seems to work as long as this custom trait is defined:The trait implementation
You just duplicate the default implementation for contiguous containers and make minimal adaptation changes and it just works.
However, that's a lot of duplication you can't get rid of and sub-classing from
flux::sequence_traits<std::vector<T>>
won't help in this case, because the functions with calls toread_at
needs to be shadowed by the derived class because they're not virtual methods, otherwise they will call the base version ofread_at
which then calls on.data()
(immer::vector
has no.data()
because it's not contiguous).virtual
also doesn't seem to work in this case as the return type needs to be covariant and there's a lot of other limitations (no return type deduce, no templates, can't be static).Relevant issue: P2279 We need a language mechanism for customization points
This is more of a "for your information" type of issue. I'm not aware of any good ways to make customization point easier to use. A workaround could be separating the trait into multiple different traits which the user can independently override, or offer a default implementation that looks for free functions which the user can provide, but that could make the customization point more confusing to use.
The text was updated successfully, but these errors were encountered: