0

I'd like to increment a counter for each parameter during parameter pack expansion. Here is some pseudo code of what I want to achieve:

    template<class ... Args>
    void doSomething(Args ... _args)
    {
        std::size_t counter = 0;
        bar(doSomethingWithOneArg(_args, counter++)...);
    }

The problem with this code is, that while the order of the Argsis preserved, the order in which the function parameters are evaluated is not defined, i.e. on clang the order in which the counter expressions are evaluated is the same as the order of Args while in gcc it is reversed. What is a portable, standard way of achieving this?

Thanks!

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
moka
  • 4,353
  • 2
  • 37
  • 63
  • 1
    Usual comment: why do you want this? – GManNickG Jun 06 '16 at 22:48
  • 1
    I think this question I asked a while back might be useful to you: http://stackoverflow.com/a/12006450/63791 – Alex Jun 06 '16 at 22:59
  • @GManNickG I am writing some Lua binding code at the moment and want to generate lua stack indices on the fly. – moka Jun 07 '16 at 00:29
  • @Alex very relevant link indeed, thanks! – moka Jun 07 '16 at 00:30
  • 1
    Here's my library with the code that uses the stuff I got from those answers: https://bitbucket.org/alexames/luawrapper/src/fd9c4fdbf4b25034e3b8475a2c8da66b7caab427/luawrapperutil.hpp?fileviewer=file-view-default#luawrapperutil.hpp-619 – Alex Jun 07 '16 at 00:32
  • @Alex great, I actually checked it out a while back, looks solid and helped me a lot to figure some things out. Ended up writing my own for reasons of error and memory management. – moka Jun 07 '16 at 00:36

3 Answers3

4

You could add a helper function:

template <typename ...Args>
void doSomething(Args &&... args)
{
    doSomethingImpl(std::make_index_sequence<sizeof...(Args)>(),
                    std::forward<Args>(args)...);
}

template <typename ...Args, std::size_t ...N>
void doSomethingImpl(std::index_sequence<N...>, Args &&... args)
{
    bar(oneCommand(std::forward<Args>(args), N)...);
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • great, didn't know about `std:: make_index_sequence`, thanks for the demo, exactly what I was looking for! – moka Jun 07 '16 at 00:32
  • I accepted this as the answer. The only thing I changed is to use my own implementation of index_sequence as that is not available in c++11. – moka Jun 07 '16 at 02:42
  • @moka: Thanks! Yes, anything that houses a pack of integers will do. The "make" part is the most tedious to have to reinvent yourself. – Kerrek SB Jun 07 '16 at 08:30
  • The "order in which the function parameters are evaluated " is still implementation defined. –  Jun 07 '16 at 09:25
  • @DieterLücking: Yeah, but now we don't care. The ordering was only a problem in the OP's code because it prevented her from computing consistent counter values. In my code, I don't need to compute those values, because I simply extract them from the pack. – Kerrek SB Jun 07 '16 at 10:35
1

If you want the order of evaluation to be in line with the order of the arguments for doSomething you can expand the arguments into an initializer:

template <typename ...Args, std::size_t ...N>
void doSomethingImpl(std::index_sequence<N...>, 
  std::tuple<Args...> && args)
{
  using std::get;
  int t_[] = { 0, (static_cast<void>(oneCommand(
    get<N>(std::forward<decltype(args)>(args)), N)), 0)... };
  static_cast<void>(t_); // "use" t_ to silence warnings 
}

template <typename ...Args>
void doSomething(Args &&... args)
{
  doSomethingImpl(std::make_index_sequence<sizeof...(Args)>(),
    std::forward_as_tuple(std::forward<Args>(args)...));
}
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
1

You might utilize list-initializatio in conjunction with a comma operator to sequence the function calls:

template<class T>
void doSomethingWithOneArg(const T&, std::size_t counter)
{
    std::cout << counter << ": " << typeid(T).name() << '\n';
}

template<class ... Args>
void doSomething(Args ... args)
{
    std::size_t counter = 0;
    std::initializer_list<int>{ ( doSomethingWithOneArg(args, counter++), 0) ... };
}

int main() {
    doSomething(char(0), int(0), long(0));
}

8.5.4 List-initialization (N4296)

4 Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.