Search code examples
c++c++17std-pairstdtuplestructured-bindings

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/


Solution

  • 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.