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?
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::array
s 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.
Self-contained preprocessor metaprogramming solution, as challenged by Yakk.
Code adapted from my own vrm_pp lightweight preprocessor metaprogramming library.
Supports 8 tuple elements.
(This is horrible. The awesome kind of horrible.)
#include <tuple>
#include <iostream>
#include <cassert>
#include <type_traits>
#define __INC_0 1
#define __INC_1 2
#define __INC_2 3
#define __INC_3 4
#define __INC_4 5
#define __INC_5 6
#define __INC_6 7
#define __INC_7 8
#define __INC_8 9
#define __NSEQ( m1, m2, m3, m4, m5, m6, m7, mN, ...) mN
#define __RSEQ() 7, 6, 5, 4, 3, 2, 1, 0
#define __CSEQ() 1, 1, 1, 1, 1, 1, 0, 0
#define __FOR_0(i, f, x)
#define __FOR_1(i, f, x, a0) f(i, x, a0)
#define __FOR_2(i, f, x, a0, a1) f(i, x, a0) __FOR_1(INC(i), f, x, a1)
#define __FOR_3(i, f, x, a0, a1, ...) f(i, x, a0) __FOR_2(INC(i), f, x, a1, __VA_ARGS__)
#define __FOR_4(i, f, x, a0, a1, ...) f(i, x, a0) __FOR_3(INC(i), f, x, a1, __VA_ARGS__)
#define __FOR_5(i, f, x, a0, a1, ...) f(i, x, a0) __FOR_4(INC(i), f, x, a1, __VA_ARGS__)
#define __FOR_6(i, f, x, a0, a1, ...) f(i, x, a0) __FOR_5(INC(i), f, x, a1, __VA_ARGS__)
#define __FOR_7(i, f, x, a0, a1, ...) f(i, x, a0) __FOR_6(INC(i), f, x, a1, __VA_ARGS__)
#define __FOR_8(i, f, x, a0, a1, ...) f(i, x, a0) __FOR_7(INC(i), f, x, a1, __VA_ARGS__)
#define __CAT_2(m0, m1) m0##m1
#define CAT_2(m0, m1) __CAT_2(m0, m1)
#define __INC(mX) __INC_##mX
#define INC(mX) __INC(mX)
#define __N_ARG(...) __NSEQ(__VA_ARGS__)
#define __ARGCOUNT(...) \
__N_ARG(__VA_ARGS__, __RSEQ())
#define ARGCOUNT(...) __ARGCOUNT(__VA_ARGS__)
#define __FOR(f, x, ...) \
CAT_2(__FOR_, ARGCOUNT(__VA_ARGS__))( \
0, f, x, __VA_ARGS__)
#define FOR(...) __FOR(__VA_ARGS__)
#define REF_TIE_BODY(mIdx, x, mArg) \
decltype(std::get<mIdx>(x)) mArg = std::get<mIdx>(x);
#define REF_TIE(tuple, ...) \
FOR(REF_TIE_BODY, tuple, __VA_ARGS__)
int main()
{
int a = 0, b = 1;
std::tuple<int &, int &> tmpTuple{a, b};
REF_TIE(tmpTuple, aref, bref);
assert(a == aref);
assert(b == bref);
static_assert(std::is_same<decltype(aref), int&>{}, "");
static_assert(std::is_same<decltype(bref), int&>{}, "");
}
(See previous edits on this answer for a vrm_pp-compliant version.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With