jaw jaw - 3 months ago 45
C++ Question

Initializing multiple references with std::tie

I'd like to have a neat way of initializing multiple references returned from a function via

std::tuple
using
std::tie
(or
std::forward_as_tuple
)—see toy code below.

#include <tuple>
#include <iostream>

class Foo
{
public:
Foo () : m_memberInt(5), m_anotherMemberInt(7) {}

void IncrementMembers() {++m_memberInt; ++m_anotherMemberInt;}

std::tuple<int &, int &> GetMembers() {return std::tie(m_memberInt, m_anotherMemberInt);}

private:
int m_memberInt;
int m_anotherMemberInt;
};

int main()
{
Foo foo;

// Can't have dangling references.
// int &x, &y;
// std::tie(x, y) = foo.GetMembers();

std::tuple<int &, int &> tmpTuple = foo.GetMembers();
int &x = std::get<0>(tmpTuple);
int &y = std::get<1>(tmpTuple);

std::cout << x << " " << y << std::endl;

foo.IncrementMembers();
std::cout << x << " " << y << std::endl;

return 0;
}


The solution above works but having the temporary
std::tuple
and multiple
std::get
s is annoying and it would be great to be able to avoid this if possible (like when returning non-references).

The issue is that we can't have dangling references so can't initialize the variables beforehand. Is there some C++11/C++14 wizardry to allow me to initialize references as I call
std::tie
? Or is the above the only solution?

Answer

In C++17 Structured Bindings writes your code for you.

std::tuple<int &, int &> tmpTuple = foo.GetMembers();
int &x = std::get<0>(tmpTuple);
int &y = std::get<1>(tmpTuple);

is roughly the same as

auto&[x,y] = foo.GetMembers();

(I might have minor syntax errors in my C++17 code, I lack experience, but you get the idea.)

You can do something similar in C++14 with continuation passing style and an adapter:

template<class Tuple>
struct continue_t {
  Tuple&& tuple;
  using count = std::tuple_size<std::remove_reference_t<Tuple>>;
  using indexes = std::make_index_sequence<count{}>;

  template<std::size_t...Is>
  auto unpacker(std::index_sequence<Is...>) {
    return [&](auto&& f)->decltype(auto){
      using std::get; // ADL enabled
      return decltype(f)(f)( get<Is>(std::forward<Tuple>(tuple))... );
    };
  };
  template<class F>
  decltype(auto) operator->*( F&& f )&& {
    auto unpack = unpacker( indexes{} );
    return unpack( std::forward<F>(f) );
  }
};
template<class F>
continue_t<F> cps( F&& f ) {return {std::forward<F>(f)};}

which, modulo typoes, gives you:

cps(foo.GetMembers())
->*[&](int& x, int&y)
{
  std::cout << x << " " << y << std::endl;

  foo.IncrementMembers();
  std::cout << x << " " << y << std::endl;
};

return 0;

which is strange. (Note that cps supports functions that return pairs or std::arrays and anything "tuple-like").

There really isn't a better way to handle this, structured bindings where added to C++17 for a reason.

A horrible preprocessor hack could probably be written that looks like:

BIND_VARS( foo.GetMembers(), x, y );

but the volume of code would be large, no compiler I know of lets you debug the mess that would be generated, you get all the strange quirks that preprocessor and C++ intersection causes, etc.