3

Suppose that I have some simple code like the one below, which just prints all of the values in a tuple and keeps track of the current iteration.

#include <iostream>
#include <tuple>
#include <utility>

using std::cout;

int main() {
    std::tuple<int, double, size_t, unsigned, short, long long, long> my_tuple(7, 4, 1, 8, 5, 2, 9);
    //Can you spot the pattern? :)
    std::apply(
        [](auto&&... current_value) {
            size_t i = 0; //This is only executed once
            ((
                cout << i++ << ", " << current_value << "\n" //This is repeated the length of the tuple
            ), ...);
        }, my_tuple
    );
    return 0;
}

If I wanted to, for example, only print the tuple value if the index was greater than 2, how could I do this? I can't simply put an if before the cout because statements are not allowed (got [cquery] expected expression on repl.it).

More generally, how can I do things like multiple lines of code or statements within a pack expansion?

Using a lambda inside works, e.g.

std::apply(
    [](auto&&... current_value) {
        size_t i = 0;
        ((
            [&current_value, &i](){
                cout << i << ", " << current_value << "\n";
                ++i;
            }()
        ), ...);
    }, my_tuple
);

But I can't imagine that this is is the most efficient (or intended) solution.

Doot
  • 555
  • 5
  • 15

1 Answers1

2

It reads much better if you declare the lambda separately and then fold over invoking it:

auto f = [&](auto& value){
    cout << i << ", " << value << "\n";
    ++i;
};
(f(current_value), ...);

You can also use something Boost.Mp11's tuple_for_each to avoid a layer of indirection:

size_t i = 0;
tuple_for_each(my_tuple, [&](auto& value){
    cout << i << ", " << value << "\n";
    ++i;
});

which is a much more direct way of doing such a thing than going through std::apply. Even if you don't want to use Boost.Mp11 (and you should want to), this is fairly easy to implement.


There was a language proposal for something called expansion statements which would make this a first-class language feature. It didn't quite make C++20, but could be in C++23:

size_t i = 0;
template for (auto& value : my_tuple) {
    cout << i << ", " << value << "\n";
    ++i;
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • is template for different proposal for the for... syntax or are they unrelated? – NoSenseEtAl Oct 02 '20 at 15:28
  • 1
    @NoSenseEtAl `for ...` got renamed to `template for`. – Barry Oct 02 '20 at 15:43
  • but co_why would somebody do that? :( I really hope it was for parsing reasons and not because people complained that only 3 dots are not obvious enough... – NoSenseEtAl Oct 02 '20 at 15:44
  • @NoSenseEtAl As you can see in my use above, there is no pack. So why choose a syntax based on packs? – Barry Oct 02 '20 at 15:55
  • depends on what ... means to you, for me it means something with a list of types(pack, variant), not just pack. Although I would probably prefer for(auto& value : my_tuple...) syntax, but that might make grammar unhappy. – NoSenseEtAl Oct 02 '20 at 16:03
  • Barry I know how much is opinion of one guy in a random comment important to wg21, but I realized why I hate the typename for (auto& value )syntax(beside syntax spam): auto already means template(for loop will work on any range). – NoSenseEtAl Oct 03 '20 at 15:26
  • @NoSenseEtAl It's not a question of what `...` means to _me_ but what rather what it manes to C++: which is either varargs or a pack, neither of which is what we're dealing with here. Also, `auto` doesn't mean `template` at all? – Barry Oct 03 '20 at 15:36
  • Barry - for(auto& x: container) in my head is the same thing as std::for_each(container, [](auto& x){}), and for_each is templated function that takes a templated functor. note:here I am pretending for_each works on containers, idk what is the matching range version, so I used std::for_each. – NoSenseEtAl Oct 04 '20 at 09:43