5

I have a function which ideally should look like this:

template<typename... t_buffers, t_function function>
void iterate_buffers(t_buffers &&... buffers, t_function &&function);

However, the parameter pack types can't be deduced unless it comes last, so instead my function signature is the following:

template<typename... t_buffers_and_function>
void iterate_bufferes(t_buffers_and_function &&... buffers_and_function);

The arguments then get shuffled around so that the function comes first. To perform this shuffling, I'm using the technique described here, but I'm hitting an issue when attempting to forward the tuple.

Here is a simplified example which has the problem:

template<typename... t_args>
void inner(std::tuple<t_args &&...> args) {};

template<typename... t_args>
void outer(t_args &&... args) {
    inner(std::forward_as_tuple(std::forward<t_args>(args)...));
}

If I pass rvalues (e.g. by calling outer(1, 2);), it works as expected. However if instead I pass lvalues (int x = 1; int y = 2; outer(x, y);), I get the following syntax error:

error: could not convert ‘std::forward_as_tuple(_Elements&& ...) [with _Elements = {int&, int&}]((* & std::forward<int&>((* & args#1))))’ from ‘std::tuple<int&, int&>’ to ‘std::tuple<int&&, int&&>’
inner(std::forward_as_tuple(std::forward<t_args>(args)...));
~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

So forward_as_tuple has produced a tuple of lvalue references std::tuple<int&, int&> as expected, since the values provided to outer are lvalues. Shouldn't inner accept a tuple of either rvalues or lvalues, since std::tuple<t_args &&...> args is using forwarding references? Why is it complaining about not being able to convert to rvalues? Are forwarding references disallowed in that context (since args is not simply T &&) so args is forced to be a tuple of rvalues only? (The examples I've found online seem to use std::tuple<t_args &&...> syntax.)

The error goes away if I remove the usage of forwarding references from inner: void inner(std::tuple<t_args...> args). Then the type of args is std::tuple<int&, int&> if I pass lvalues to outer, and std::tuple<int&&, int&&> if I pass rvalues. Maybe forwarding references aren't needed here because the correct lvalue/rvalue types have already been encoded into the tuple and don't need forwarding? I'm confused because all the examples I see online (including the one I linked) use the original call signature for inner which has forwarding references.

Gumgo
  • 526
  • 4
  • 16
  • A smaller example is "Why can't `template void test(std::tuple);` be called as `test(std::tuple{i});` (with `T = int&`)". A minimal example here: https://godbolt.org/z/nh19of – Artyer Aug 26 '20 at 00:57
  • I'm guessing the problem is that `std::tuple` isn't a tuple of a forwarding reference, but rather is a tuple of rvalue references? [Scott Meyers gives two examples:](https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers) `f(std::vector&&)` is an rvalue reference and `f(T &&)` is a forwarding reference. Perhaps my misunderstanding is that I thought anytime `T&&` appears in a parameter, that portion of the type gets interpreted as a forwarding reference (the vector example still fails under that rule), but actually, only `T&&` _exactly_ as the whole parameter works? – Gumgo Aug 26 '20 at 17:53

0 Answers0