Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Name alias references for pair or tuple values

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/

like image 336
darune Avatar asked Jul 15 '26 04:07

darune


2 Answers

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.

like image 65
Peter Ruderman Avatar answered Jul 17 '26 17:07

Peter Ruderman


In C++17, I would simply go with:

struct Transaction
{
    int deducted;
    int transfered;
};

With usage similar to:

Transaction makeTheTransaction() { return {4, 2}; }

int main()
{
    auto [deduct, transfer] = makeTheTransaction(); 
    std::cout << deduct << transfer << std::endl;
}

Demo

like image 30
Jarod42 Avatar answered Jul 17 '26 16:07

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!