Container Algorithms
ericniebler.com
ericniebler.com
On the other hand, I always worry about all the magic implicit behavior in C++, both in old and new language features. We're writing in C++, we care about efficiency. Eric addresses this by requiring rvalue references in the container algorithms, but in general it's pretty easy to write code that does more work than you expected.
Since most undesired magic in C++ boils down to allocating heap memory unexpectedly, it would be cool to add syntax to could tell the compiler that a block is not allowed to allocate, like:
noalloc {
bigvec = std::move(bigvec) | cont::sort | cont::unique;
}Why can't the
std::vector<int> ints =
read_ints() | cont::sort | cont::unique;
from TFA can't be done "as lazily as possible" (i.e., sort eagerly and uniq lazily). Alternatively, something like std::vector<int> ints(read_ints() | cont::sort | cont::unique);
or even std::vector<int> ints;
read_ints() | cont::sort | cont::unique > ints;
What reason could we have for making `read_ints` and `unique` eager by default?Performance-wise, the lazy filter works in constant space, will result in fewer dcache misses if the data set is large, and (I think) provides more opportunity for the optimizer to do clever things. The eager chain may have better icache performance if the filter is very complicated.
And it's easy to go from lazy to eager -- just put everything in a vector. If you want to go from eager to lazy, though, you're out of luck.
I agree with your side of the trade-off, a performance bug (result: slow application) is always preferable to a memory bug (result: failed malloc, likely crash) in production.
To make things more desirable, you could always do something even simpler than your example:
auto ints = read_ints() | cont::sort | cont::unique | cont:to_vector;
That way fixing the performance bug would only need a one-liner.Still, having the option is better than nothing.
Looking at your code:
std::vector<int> ints;
read_ints() | cont::sort | cont::unique > ints;
If `cont::unique` is lazy (non-owning), what is the lifetime of the elements allocated by `cont::sort` (or moved from `read_ints()`)? If the code had been written instead like below, who is responsible for keeping the elements alive? std::vector<int> ints;
auto tmp = read_ints() | cont::sort | cont::unique;
tmp > ints;
There's a whole host of issues here that you're not dealing with.The first mayor library to use this syntax was Boost.Range and it is now being proposed as the syntax of standard ranges.
> Eric Niebler contributed the Range Adaptor idea which is arguably the single biggest innovation in this library.
- flat set/map - multi_index - static_vector (called SmallVector in LLVM) - bimap - intrusive containers - various from Boost.Heap