-
-
Couldn't load subscription status.
- Fork 23.5k
Add zip_shortest and enumerate iteration helpers.
#111916
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
base: master
Are you sure you want to change the base?
Conversation
|
I'd argue that it depending on |
I don't have as hard of a dislike against It would also be possible to use explicit const float floats[5];
for (Tuple<size_t, const float &> tuple : enumerate(floats)) {
// ...
}If we don't want auto, I may prefer #111492. But unfortunately, it would be incompatible with It's a real shame that C++ destructuring does not support explicit types! |
|
(Separated to a followed comment) My issue with Take for example this: // In some header file, the grandparent class of the class we're using.
Vector<Foo> my_foos;
// In the source itself, with no direct idea of what `my_foos` contains, without searching up the hierarchy.
for (auto [idx, val] : enumerate(my_foos)) {
// How to know what `val` is if I'm just reading this code?
}Contrasted with having the type explicit, where you don't have to go hunting for the type to know what it does |
51f1b06 to
9617a00
Compare
|
This preference and concern comes in quite a big part from doing review where being able to tell what's going on is important to be able to evaluate what's going on in a bit of code, and a lot of just looking at the code to understand it would be done just looking at the code listing on GitHub I'd say But where explicit typing can be achieved using for (auto [idx, var] : get_something_from_great_grandparent(foo)) {
// ...
}Edit: And to be clear I used to use |
|
Hmmm… There might be a way to sidestep I played around with this a bit locally; here's a rough example of what I mean: template <typename T, bool EXPLICIT = true>
class ForceExplicit {
T _value;
public:
ForceExplicit(T p_value) :
_value(p_value) {
static_assert(EXPLICIT, "Template must be explicitly defined");
}
};
template <typename T>
ForceExplicit(T) -> ForceExplicit<T, false>; // User-defined deduction to invalidate implicit construction.
void test_explicit() {
// auto test_implicit = ForceExplicit(0); // Fails compilation, static assertion triggered.
auto test_explicit = ForceExplicit<int>(0); // Will compile as expected.
}The hope is that this would allow us to have our cake and eat it too, where we can use this neat syntax that forces // Hypothetical implementation
Vector<Foo> my_foos;
// for (auto [idx, val] : enumerate(my_foos)) {} // Fails compilation, static assertion triggered.
for (auto [idx, val] : enumerate<size_t, Foo&>(my_foos) {} // Will compile as expected. |
|
Your suggestion has the additional advantage that types can be coerced on return (like in regular for loops): const int ints[5];
// Will all work
for (auto [idx, val] : enumerate<int, int&>(ints) {}
for (auto [idx, val] : enumerate<int, const int&>(ints) {}
for (auto [idx, val] : enumerate<int, int>(ints) {}I like it! |
|
I'd say that does a lot to reduce the issue with type clarity, though I'd say it still creates an ambiguity in enforcing no- |
|
I'm also concerned that for (int i = 0; i < MIN(a.size(), b.size()), ++i) {
av = a[i];
bv = b[i];
// ...
}The hidden implementation details might throw people off as it's far less clear what it does when iterating differently sized containers, by hiding the implementation details I'd also be interested in knowing what the potential impact of using templates so much is on compilation performance and binary size, the templates should be inlined but that's a potential hazard, and especially the compilation time of working out the templates and substituting them Also reiterating my concern from the other PR that this should come as a solution to existing code needs, would be good to have some cases where this would be used in the current codebase to evaluate the need and demand for it |
|
I think the concern of hiding implementation details is valid, though I would argue that could be applied to the majority of core templates. We could make it a requirement that PRs adding core templates need an associated documentation PR as well. That way these ambiguous cases would have something concrete for us to point towards, with the niches they fill having associated examples and "best practices" |
9617a00 to
7a12219
Compare
|
I've pushed a change that includes Repiteo's suggestion. I also share ATS's concern that
Yea, this will definitely be harder to compile than regular iterators. However, the cost most likely comes when it's used, so it would likely be confined to a few dozen
gcc and clang are very good, so I expect they will optimize this perfectly. MSVC will probably be "good enough" (as always). |
zip and enumerate iteration helpers.zip_shortest and enumerate iteration helpers.
|
The difference is that we don't really have any other templates like this, we have containers that are complex data types, but this is a simple wrapper or helper that has very little obvious meaning in the specifics IMO
I think practically any performance overhead for compiling for helper types is too much, especially since the readability is arguably worsened at least personally If they come at a cost, and have to be used sparingly, that feels like it defeats the purpose, especially without good examples of where to use things |
We have a few helpers of varying complexity in core headers. Some examples include But yea, this would be the first one for iteration, which is already quite nebulous in C++ imo.
Readability is definitely a matter of perspective. I'd argue that it's easier to read if you accept it as a blackbox implementation (like language features), or if you are quite familiar with it already.
If we were to introduce them, I would fully encourage their use in all cases they can be used. I don't think there's a need to use them "sparingly". |
|
I based that on the following which seemed to be saying that the cost would be negligible in practice because they were used sparingly:
But I'd say that indeed it would be good to:
|
|
Makes sense, what I meant was "I don't think it will be useful very often" rather than "I don't think we should use them very often".
Fully agreed! |

Enumeratetemplate #111492Adds
zip_shortestandenumerateiteration helpers.If they're needed, I'll take this PR out of draft.
Note that I'm not 100% sure we actually want this, due to its trade-offs.
How to use
Trade-off
There are some downsides to the implementation:
auto.That being said, there are upsides:
enumerateandzipwork very similarly as in other languages (e.g. python, Rust), and should be instantly familiar.Notes
Part of the implementation adds
std::getsupport to ourTuple. This would also allow code like this:We may wish to merge this separately, although there is no explicit reason to do it (and destructuring uses
autoanyway).