преобразование структуры в/из std::tuple

Предполагая, что у меня есть struct и std::tuple с макетом одного типа:

struct MyStruct { int i; bool b; double d; }
using MyTuple = std::tuple<int,bool,double>;

Есть ли какой-нибудь стандартный способ приведения одного к другому?

P.S. Я знаю, что тривиальное копирование памяти может помочь, но это зависит от выравнивания и реализации.


person Andrei R.    schedule 25.07.2016    source источник
comment
P.S. Я знаю, что тривиальное копирование памяти может помочь std::tuple<> не гарантирует тривиальность, поэтому копирование памяти здесь будет UB.   -  person ildjarn    schedule 25.07.2016
comment
Возможный дубликат TMP: как написать код шаблона, который преобразует любую структуру в кортеж?   -  person mash    schedule 25.07.2016
comment
@ildjarn на самом деле нет никаких гарантий даже в отношении порядка, в котором элементы хранятся внутри. Кроме того, агрессивная оптимизация пространства пользовательских классов с отступами и пустыми классами также может испортить вам день.   -  person Revolver_Ocelot    schedule 25.07.2016
comment
memcpy звучит как ужасная идея...   -  person Rerito    schedule 25.07.2016
comment
Существует библиотека 'magic_get', которая выполняет самоанализ структур.   -  person Ilya Popov    schedule 29.07.2016
comment
Я обновил свое решение, чтобы оно было действительным, даже если члены не являются конструируемыми по умолчанию.   -  person alfC    schedule 29.07.2016
comment
@IlyaPopov, эта библиотека кажется довольно удивительной, я до сих пор не понимаю, как возможно, что они достигают такого уровня самоанализа. (Мое лучшее предположение состоит в том, что используется какой-то умный макрос, замаскированный под struct)   -  person alfC    schedule 29.07.2016
comment
@alfC Насколько я понимаю, они используют тот же трюк, что и в ответе Якка.   -  person Ilya Popov    schedule 30.07.2016
comment
Обновление: этот трюк, очевидно, можно использовать только в режиме С++ 17. В режиме C++14 они используют что-то другое. Если я правильно помню, автор однажды объяснил, как это работает, в списке рассылки boost.devel, но, похоже, сегодня он не работает.   -  person Ilya Popov    schedule 30.07.2016


Ответы (4)


К сожалению, нет автоматического способа сделать это, НО альтернативой является адаптация структуры к последовательности Boost.Fusion. Вы делаете это раз и навсегда для каждого нового класса.

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
...
struct MyStruct { int i; bool b; double d; }

BOOST_FUSION_ADAPT_STRUCT(
    MyStruct,
    (int, i)
    (bool, b)
    (double, d)
)

Используйте MyStruct так, как если бы это был Fusion.Sequence (в общем, он подходит почти везде, где вы уже используете std::tuple<...>, если вы сделаете эти функции универсальными). В качестве бонуса вам вообще не нужно копировать элементы данных.

Если вам действительно нужно конвертировать в std::tuple, после "Fusion-адаптации" вы можете сделать это:

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/algorithm/transformation/zip.hpp>
...
auto to_tuple(MyStruct const& ms){
   std::tuple<int, bool, double> ret;
   auto z = zip(ret, ms);
   boost::fusion::for_each(z, [](auto& ze){get<0>(ze) = get<1>(ze);});
   // or use boost::fusion::copy
   return ret;
}

Правда в том, что std::tuple — это половинчатая функция. Это как иметь STD-контейнеры и никаких алгоритмов. К счастью, у нас есть #include <boost/fusion/adapted/std_tuple.hpp>, который позволяет нам делать удивительные вещи.

Полный код:

При включении заголовка std_tuple.hpp из Boost.Fusion std::tuple автоматически адаптируется к последовательности Boost.Fusion, поэтому при использовании Boost.Fusion в качестве моста между вашей структурой и std::tuple возможно следующее:

#include <iostream>
#include <string>
#include <tuple>

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/algorithm/auxiliary/copy.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>

struct foo
{
  std::string a, b, c;
  int d, e, f;
};

BOOST_FUSION_ADAPT_STRUCT(
    foo,
    (std::string, a)
    (std::string, b)
    (std::string, c)
    (int, d)
    (int, e)
    (int, f)
)

template<std::size_t...Is, class Tup>
foo to_foo_aux(std::index_sequence<Is...>, Tup&& tup) {
  using std::get;
  return {get<Is>(std::forward<Tup>(tup))...};
}
template<class Tup>
foo to_foo(Tup&& tup) {
  using T=std::remove_reference_t<Tup>;
  return to_foo_aux(
    std::make_index_sequence<std::tuple_size<T>{}>{},
    std::forward<Tup>(tup)
  );
}

template<std::size_t...Is>
auto to_tuple_aux( std::index_sequence<Is...>, foo const& f ) {
  using boost::fusion::at_c;
  return std::make_tuple(at_c<Is>(f)...);
}
auto to_tuple(foo const& f){
  using T=std::remove_reference_t<foo>;
  return to_tuple_aux(
    std::make_index_sequence<boost::fusion::result_of::size<foo>::type::value>{},
    f
  );    
}

int main(){


    foo f{ "Hello", "World", "!", 1, 2, 3 };

    std::tuple<std::string, std::string, std::string, int, int, int> dest = to_tuple(f);
    // boost::fusion::copy(f, dest); // also valid  but less general than constructor

    std::cout << std::get<0>(dest) << ' ' << std::get<1>(dest) << std::get<2>(dest) << std::endl;
    std::cout << at_c<0>(dest) << ' ' << at_c<1>(dest) << at_c<2>(dest) << std::endl; // same as above

    foo f2 = to_foo(dest);

    std::cout << at_c<0>(f2) << ' ' << at_c<1>(f2) << at_c<2>(f2) << std::endl;
}

Я не буду рекомендовать reinterpret_cast<std::tuple<...>&>(mystructinstance.i), потому что это приведет к отрицательному голосованию и не будет переносимым.

person alfC    schedule 25.07.2016
comment
Я не буду рекомендовать reinterpret_cast<std::tuple<...>&>(mystructinstance.i), потому что это приведет к отрицательному голосованию, и оно не будет переносимым. Вам не следует рекомендовать его, потому что это совершенно незаконно, не только потому, что за вас проголосуют. ;-] +1 за упоминание Fusion! - person ildjarn; 25.07.2016
comment
Мало того, что это совершенно незаконно, нет никакой гарантии, что это будет работать во всех реализациях. Или даже некоторые реализации. - person Nicol Bolas; 25.07.2016
comment
@ildjam Сегодня я немного демагог (это текущая тенденция), я больше беспокоюсь о минусах, чем о легальности C ++. :) - person alfC; 25.07.2016
comment
@NicolBolas, да, именно поэтому я сказал, что он не переносим, ​​даже если он работает в какой-то системе. - person alfC; 25.07.2016
comment
Если вы включаете заголовок boost fusion std_tuple, он адаптирует std::tuple, чтобы вы могли просто вызвать boost::fusion::copy() для копирования из адаптированной структуры в std::tuple... вам не нужно использовать свою пользовательскую итерацию. последовательность слияния, поэтому копирование в любом направлении должно быть возможно (и оно достаточно умно, чтобы обрабатывать повторяющиеся типы..) - person Nim; 26.07.2016
comment
Я позволил себе добавить пример для копирования, я думаю, что это делает этот ответ полным - если вы чувствуете, что это не имеет значения, удалите его. - person Nim; 26.07.2016
comment
@Ним, да, спасибо. Я был уверен, что существует copy алгоритм`, но я не смог найти его в основном руководстве. Это было во вспомогательном разделе boost.org/doc/libs/1_61_0/libs/fusion/doc/html/fusion/algorithm/ . Чего я еще толком не понял, так это того, как сгенерировать структуру из кортежа, если элементы не являются конструктивными по умолчанию, возможно, нет способа. - person alfC; 26.07.2016
comment
@Nim, я понял, как это сделать, даже если типы членов не являются конструируемыми по умолчанию. boost::fusion::copy можно использовать, но не обязательно. - person alfC; 29.07.2016

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

Struct-to-tuple очень неудобно.

template<std::size_t N>
struct to_tuple_t;

template<>
struct to_tuple_t<3> {
  template<class S>
  auto operator()(S&& s)const {
    auto[e0,e1,e2]=std::forward<S>(s);
    return std::make_tuple(e0, e1, e2);
  }
};

Теперь напишите to_tuple_t для каждого размера, который вы хотите поддерживать. Это становится утомительным. К сожалению, я не знаю, как добавить туда пакет параметров.

template<std::size_t N, class S>
auto to_tuple(S&& s) {
  return to_tuple_t<N>{}(std::forward<S>(s));
}

Я также не знаю, как рассчитать требуемое значение N. Таким образом, вам нужно будет ввести 3 в auto t = to_tuple<3>(my_struct);, когда вы его вызываете.

Я не мастер структурированных привязок. Вероятно, существует && или &, или decltype, который позволит идеально пересылать следующие строки:

    auto[e0,e1,e2]=std::forward<S>(s);
    return std::make_tuple(e0, e1, e2);

но без компилятора я буду консервативен и буду делать лишние копии.


Преобразовать кортеж в структуру очень просто:

template<class S, std::size_t...Is, class Tup>
S to_struct( std::index_sequence<Is...>, Tup&& tup ) {
  using std::get;
  return {get<Is>(std::forward<Tup>(tup))...};
}
template<class S, class Tup>
S to_struct( Tup&&tup ) {
  using T=std::remove_reference_t<Tup>;

  return to_struct(
    std::make_index_sequence<std::tuple_size<T>{}>{},
    std::forward<Tup>(tup)
  );
}

Поддержка SFINAE, основанная на tuple_size, может быть полезной для to_struct.

Приведенный выше код работает со всеми подобными кортежами, такими как std::pair, std::array, и всем, что вы настроили для поддержки структурированных привязок (tuple_size и get<I>).


Забавно,

std::array<int, 3> arr{1,2,3};
auto t = to_tuple<3>(arr);

работает и возвращает кортеж с 3 элементами, так как to_tuple основан на структурированных привязках, которые работают с подобными кортежами в качестве входных данных.

to_array - еще одна возможность в этой семье.

person Yakk - Adam Nevraumont    schedule 25.07.2016
comment
@rusty Мне не нравятся нарушения псевдонимов в этом. Зачем переинтерпретировать преобразование в указатель на то, чем он не является? У UB должен быть огромный выигрыш, прежде чем даже рассматривать его. - person Yakk - Adam Nevraumont; 22.08.2017
comment
Я также не знаю способа вычислить требуемое значение N, верхний предел равен sizeof(T), поэтому вы можете протестировать инициализацию агрегата до этого предела, что-то вроде что. - person Jarod42; 17.08.2020
comment
@ Jarod42 Jarod42 Я думаю, что у вас достаточно элементов без уникального адреса. Но да, это действительно близко. - person Yakk - Adam Nevraumont; 17.08.2020
comment
Действительно... - person Jarod42; 17.08.2020

Есть ли какой-нибудь стандартный способ приведения одного к другому?

Невозможно «привязать» одно к другому.

Проще всего использовать std::tie для упаковки кортежа в struct;

struct MyStruct { int i; bool b; double d; };
using MyTuple = std::tuple<int,bool,double>;

auto t = std::make_tuple(42, true, 5.1);
MyStruct s;

std::tie(s.i, s.b, s.d) = t;

Демо.

Вы можете дополнительно обернуть это макросами более высокого уровня или функциями «генератора» (стиль make), например;

std::tuple<int, bool, double> from_struct(MyStruct const& src)
{
  return std::make_tuple(src.i, src.b, src.d);
}

MyStruct to_struct(std::tuple<int, bool, double> const& src)
{
  MyStruct s;
  std::tie(s.i, s.b, s.d) = src;
  return s;
}

Я знаю, что тривиальное копирование памяти может помочь, но это зависит от выравнивания и реализации?

Вы упомянули, что «тривиальная копия памяти» будет работать - только для копирования отдельных членов. Таким образом, memcpy всей структуры в tuple и наоборот не всегда будет вести себя так, как вы ожидаете (если вообще когда-либо); схема памяти tuple не стандартизирована. Если это работает, это сильно зависит от реализации.

person Niall    schedule 25.07.2016
comment
Я ищу более общее решение. Что, по крайней мере, не требует обращения к каждому члену структуры. - person Andrei R.; 25.07.2016
comment
Все решения, которые я видел за эти годы, которые требуют индивидуального доступа к членам (т.е. ссылки на каждого), делают именно это (часто с использованием макросов); примеры здесь включают ускоренную сериализацию и т. д. Боль можно облегчить с помощью скриптовой реализации, скрипт может генерировать шаблонный код, вы также можете включить его в сборку, чтобы он всегда был правильным и актуальным для компилируемых структур. К сожалению, для этих вещей нет стандартного преобразования. - person Niall; 25.07.2016

Преобразование кортежа в struct тривиально, но обратное, я думаю, невозможно на текущем уровне C++ в целом.

#include <type_traits>
#include <utility>

#include <tuple>

namespace details
{

template< typename result_type, typename ...types, std::size_t ...indices >
result_type
make_struct(std::tuple< types... > t, std::index_sequence< indices... >) // &, &&, const && etc.
{
    return {std::get< indices >(t)...};
}

}

template< typename result_type, typename ...types >
result_type
make_struct(std::tuple< types... > t) // &, &&, const && etc.
{
    return details::make_struct< result_type, types... >(t, std::index_sequence_for< types... >{}); // if there is repeated types, then the change for using std::index_sequence_for is trivial
}

#include <cassert>
#include <cstdlib>

int main()
{
    using S = struct { int a; char b; double c; };
    auto s = make_struct< S >(std::make_tuple(1, '2', 3.0));
    assert(s.a == 1);
    assert(s.b == '2');
    assert(s.c == 3.0);
    return EXIT_SUCCESS;
}

Живой пример.

person Tomilov Anatoliy    schedule 25.07.2016
comment
Вам нужно немного потанцевать с индексами, если типы не различаются. - person Yakk - Adam Nevraumont; 25.07.2016
comment
@Yakk Также возможна правильная пересылка содержимого кортежа .. - person Tomilov Anatoliy; 03.08.2016