1

See here for sample code: https://godbolt.org/z/o5osbq3bY

Hi everyone,

I'm working on some code that pulls values from a virtual machine. The interface requires both the type of the value and its index on the stack, which I've mocked into T get_one(int stack_pos). I'm writing a wrapper that will pull N values from the stack and pushes to a C++ function.

In the sample, if you define I_LOVE_RVALUES, the code uses std::forward_as_tuple Otherwise, it'll use std::make_tuple. I want to forward the values from the VM to the C++ function with as little overhead as possible, so at first I tried with the pass-by-rvalue. However, I'm getting corrupted values.

Following the advice of this thread: C++11 std::forward_as_tuple and std::forward I've thrown std::forward's everywhere, but it still doesnt work

I've switched to the pass by value version now and it works fine, but I want to understand why the Rvalue version doesn't work. My understanding is that I followed all the rules:

  1. Each rvalue is either used or forwarded into another Rvalue reference
  2. The tuple is only passed into std::apply and never used again, so its rvalues can be consumed.

My best guess is there's some interaction between the Rvalue-tuples and std::tuple_cat, but I don't really understand what's going on.

Full sample code listing:

#include <iostream>
#include <string>
#include <vector>
#include <tuple>
#include <cxxabi.h>

#define I_LOVE_RVALUES

#ifdef I_LOVE_RVALUES
#define tuple_creator std::forward_as_tuple
#else
#define tuple_creator std::make_tuple
#endif


template<typename T>
T get_one(int stack_pos)
{
    // std::cout << typeid(T).name() + std::to_string(i) << std::endl;
    return static_cast<T>(stack_pos);
}

template<typename T>
inline auto get_all(int i)
{
    return tuple_creator(std::forward<T>(get_one<T>(i)));
}
// recursive case
template<typename T0, typename T1, typename ...T>
inline auto get_all(int i)
{
    return std::tuple_cat(
        tuple_creator(std::forward<T0>(get_one<T0>(i))),
        get_all<T1, T...>(i+1)
    );
}

void print_all(int i, float f1, float f2)
{
    std::cout << i << f1 << f2 << std::endl;
}

int main()
{
    auto&& tup = get_all<int, float, float>(0);
    std::string tup_typeid_name = typeid(decltype(tup)).name();
    std::apply(print_all, tup);
    // here i'll make my forward_as_tuple directly
    std::apply(print_all, std::forward_as_tuple(std::move(0), std::move(1), std::move(2)));
    std::cout << tup_typeid_name << std::endl;
}

If I_LOVE_RVALUES is defined, the first line is garbage. Otherwise, it should read 012

The second line should always read 012

saltyJeff
  • 67
  • 1
  • 4

1 Answers1

0

get_one returns a prvalue. To pass that to forward_as_tuple, a temporary object is created and then the resulting tuple refers to that temporary. But that temporary is going to be destroyed at the end of the full-expression containing the call - in your case, as soon as get_all returns.

In the end, forward_as_tuple produces a tuple of references, but the things referred to still have to be stored somewhere.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Why does get_all destroy the temporary? The call stack looks like this: 1. auto&& tup in main() is an rvalue 2. get_all uses tuple_cat, which takes in tuples as an rvalue 3. the tuples passed into tuple_cat use forwarding to construct each element, which preserves rvalue semantics At what point does the rvalue stop prolonging the lifetimes? – saltyJeff Apr 21 '23 at 20:29