When restructuring some code I came across a 'problem' when returning a struct with 2 values. Now these really should be named for the documented effect. Later on I wanted to use tie
so i changed the struct into inheriting from std::pair
and just setting references. Now this actually works fine, but you will notice now my struct has the size of 24 as opposed to just 8 compared to the pair.
#include <tuple>
struct Transaction : public std::pair<int, int> {
using pair::pair;
int& deducted = first;
int& transfered = second;
};
//static_assert(sizeof(Transaction) == sizeof(std::pair<int, int>));//commenting in this line will fail compilation
Transaction makeTheTransaction();
void test(int& deduct, int& transfer) {
std::tie(deduct, transfer) = makeTheTransaction();
}
The maybe obvious method is to change into member functions, however that is also too much 'boilerplate' for this case (then it just becomes easier not use tie
later on). A direct memcpy is to eg. a tuple is straigt forward UB. A direct structured binding is also not doable since the variables is already in use.
My question is what is a better or minimal code solution disregarding reusable parts (and given that the size shouldn't grow beyond the size of 2 ints) ?
UPDATE: For this case I ended up just doing a plain struct and hold return in a temporary. For other users coming here, there is a library proposal to boost that seems to be able to convert any struct to tuple: https://github.com/apolukhin/magic_get/
It seems like you're over-complicating the problem to me. If you need to use std::tie
, you can just use it. There's no need to alter your structure:
struct Transaction
{
int deducted;
int transferred;
};
// later...
auto t = std::tie(transaction.deducted, transaction.transferred);
If this is a pattern you use frequently, then you can wrap it in a little helper method:
struct Transaction
{
int deducted;
int transferred;
auto to_tuple() const
{
return std::tie(deducted, transferred);
}
};
You can also use this to assign to multiple variables at once, although I strongly discourage that. It's error prone and leads to brittle code. (For example, if you reverse the order of deduct
and transfer
in the example below, you've got a bug, but the compiler will give no warning or error.)
void test(int& deduct, int& transfer)
{
std::tie(deduct, transfer) = makeTheTransaction().to_tuple();
}
Edit: On second thought...
If the goal here is just easy decomposition of the struct into variables, you could do that directly and avoid using pairs or tuples:
struct Transaction
{
int deducted;
int transferred;
void decompose(int* deducted_, int* transferred_)
{
*deducted_ = deducted;
*transferred_ = transferred;
}
};
void test(int& deduct, int& transfer)
{
makeTheTransaction().decompose(&deduct, &transfer);
}
This is still brittle, but at least now you'll get intellisense when you write the call to the decompose
method, which will make the pattern a little less error prone.