Ссылки на псевдонимы имен для значений пар или кортежей

При реструктуризации некоторого кода я столкнулся с «проблемой» при возврате структуры с двумя значениями. Теперь они действительно должны быть названы в честь задокументированного эффекта. Позже я хотел использовать tie, поэтому я изменил структуру на наследование от std::pair и просто установил ссылки. Теперь это действительно работает нормально, но вы заметите, что теперь моя структура имеет размер 24, а не 8 по сравнению с парой.

#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(); 
}

Возможно, очевидный метод состоит в том, чтобы перейти к функциям-членам, однако это также слишком много «шаблона» для этого случая (тогда становится проще не использовать tie позже). Прямой memcpy, например. кортеж является прямым UB. Прямая структурная привязка также невозможна, поскольку переменные уже используются.

Мой вопрос заключается в том, что является лучшим или минимальным кодовым решением, игнорирующим повторно используемые части (и учитывая, что размер не должен превышать размер 2 целых чисел)?

ОБНОВЛЕНИЕ: В этом случае я просто сделал простую структуру и удержал возврат во временном. Для других пользователей, приходящих сюда, есть предложение библиотеки для повышения, которое, кажется, может преобразовать любую структуру в кортеж: https://github.com/apolukhin/magic_get/


person darune    schedule 20.11.2018    source источник
comment
Просто: struct Transaction {int deducted; int transfered; }; ?   -  person Jarod42    schedule 20.11.2018
comment
@ Jarod42, это было бы хорошо, но не удалось бы задокументировать, что возвращает makeTheTransaction().   -  person darune    schedule 20.11.2018
comment
Зачем вообще нужно наследовать от std::pair? Вы могли бы просто заставить makeTransaction вернуть старую простую пару, поскольку вы, похоже, не используете вычитаемые и переданные поля (которые не будут работать так, как написано, ссылки должны быть установлены в конструкторе).   -  person jwimberley    schedule 20.11.2018
comment
Наследование std::pair — это UB.   -  person felix    schedule 20.11.2018
comment
Я не понимаю, почему вам нужно наследовать от std::pair, чтобы использовать std::tie. Просто используйте его: auto t = std::tie(transaction.deducted, transaction.transferred).   -  person Peter Ruderman    schedule 20.11.2018
comment
Мне не нравилось наследование пары с самого начала. Но, как я уже сказал, для документирования результатов функции.   -  person darune    schedule 20.11.2018


Ответы (3)


Мне кажется, вы слишком усложняете проблему. Если вам нужно использовать std::tie, вы можете просто использовать его. Нет необходимости изменять структуру:

struct Transaction
{
  int deducted;
  int transferred;
};

// later...

auto t = std::tie(transaction.deducted, transaction.transferred);

Если вы часто используете этот шаблон, вы можете обернуть его небольшим вспомогательным методом:

struct Transaction
{
  int deducted;
  int transferred;

  auto to_tuple() const
  {
    return std::tie(deducted, transferred);
  }
};

Вы также можете использовать это для одновременного назначения нескольких переменных, хотя я настоятельно не рекомендую это делать. Это подвержено ошибкам и приводит к хрупкому коду. (Например, если вы поменяете порядок deduct и transfer в приведенном ниже примере, у вас будет ошибка, но компилятор не выдаст предупреждения или ошибки.)

void test(int& deduct, int& transfer)
{
  std::tie(deduct, transfer) = makeTheTransaction().to_tuple();
}

Правка: если подумать…

Если целью здесь является простое разложение структуры на переменные, вы можете сделать это напрямую и избежать использования пар или кортежей:

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);
}

Это все еще хрупко, но, по крайней мере, теперь вы получите intellisense, когда будете писать вызов метода decompose, что сделает шаблон немного менее подверженным ошибкам.

person Peter Ruderman    schedule 20.11.2018
comment
Ваше первое предложение с простой структурой также «болезненно». Хотя, возможно, это и не хрупко, как вы говорите, это приводит к чрезмерно сложному коду. Дело в том, что сначала мне нужно создать и назвать временную переменную для хранения возвращаемой структуры. Затем я должен назначить другим 2 переменным - теперь 3 строки кода. Затем, чтобы не загрязнять остальную часть временной областью, я должен поместить блочную область вокруг этих трех строк. - person darune; 21.11.2018
comment
Ну, я бы предложил две вещи. Во-первых, набор текста не является узким местом. Экономия нескольких строк при наборе редко стоит того, чтобы открыть возможность будущих ошибок. Мы должны стремиться к правильному по построению коду. Во-вторых, передача всей структуры кажется более простым вариантом. Почему необходимо разбивать структуру на несколько переменных? - person Peter Ruderman; 21.11.2018
comment
Я согласен с тем, что обход лучше делать со структурой. Тем не менее, я обнаружил, что иногда при работе с существующим (устаревшим) кодом и его реструктуризации вы хотите выполнять только один или несколько шагов за раз по разным причинам (например, одна из причин заключается в том, что вы не знаете размер окружающего кода). это нужно обновить - другой не слишком теряет ваш фокус). Теперь я просто сделал простую структуру и вернулся во временную (на данный момент). Спасибо за ответ. - person darune; 21.11.2018

Я бы просто пошел с:

struct Transaction
{
    int deducted;
    int transfered;
};

С использованием аналогично:

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

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

Демо

person Jarod42    schedule 20.11.2018
comment
Предполагаете, что это специфично для С++ 17? - person jwimberley; 20.11.2018
comment
Он должен работать с ранее объявленными переменными. Я как-то забыл, что в вопросе.. - person darune; 20.11.2018
comment
Я обновил код примера, чтобы показать, что тестовая функция возвращает значение по параметру. - person darune; 20.11.2018

Добавление функции преобразования работает:

#include <tuple>

struct Transaction {
    std::pair<int, int> data_;

    operator std::tuple<int &, int &> () {
        return std::tie(data_.first, data_.second);
    }
};
static_assert(sizeof(Transaction) == sizeof(std::pair<int, int>));

Transaction makeTheTransaction() {
    return Transaction();
}

void test(int& deduct, int& transfer) {
    std::tie(deduct, transfer) = makeTheTransaction();
}

Я не думаю, что это вызовет какие-либо проблемы с продолжительностью жизни при использовании с std::tie.

Этот Transaction не работает со структурированной привязкой с двумя идентификаторами. Но вы можете сделать так, чтобы он поддерживал привязку типа «кортежа», настроив для него std::get и std::tuple_size.

person felix    schedule 20.11.2018