结构 to/from std::tuple 转换
struct to/from std::tuple conversion
假设我有 struct
和 std::tuple
具有相同类型的布局:
struct MyStruct { int i; bool b; double d; }
using MyTuple = std::tuple<int,bool,double>;
有没有标准化的方法可以将一个转换为另一个?
P.S。我知道简单的内存复制可以解决问题,但它依赖于对齐和实现
Is there any standartized way to cast one to another?
没有办法 "cast" 一个到另一个。
最简单的方法可能是使用 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;
Demo.
您可以进一步将其包装在更高级别的宏或 "generator"(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;
}
I know that trivial memory copying can do the trick, but it is alignment and implementation dependent?
您提到 "trivial memory copy" 会起作用 - 仅用于复制单个成员。所以基本上,从 tuple
到 tuple
的整个结构的 memcpy
和反之亦然不会总是像你期望的那样表现(如果有的话); tuple
的内存布局未标准化。如果确实有效,则高度依赖于实现。
不幸的是,没有自动的方法来做到这一点,但另一种方法是使结构适应 Boost.Fusion 序列。您为每个新的 class.
一次性执行此操作
#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-adapting"之后您可以这样做:
#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
是一个 half-backed 特征。这就像有 STD 容器而没有算法。幸运的是,我们有 #include <boost/fusion/adapted/std_tuple.hpp>
可以让我们做出惊人的事情。
完整代码:
通过包含 Boost.Fusion 中的 std_tuple.hpp
header 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)
,因为那会导致投反对票并且不可移植。
元组到 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;
}
我们可以使用结构化绑定将结构转换为元组,只需一些工作。
结构到元组非常尴尬。
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
的值。所以你必须在调用它时在 auto t = to_tuple<3>(my_struct);
中键入 3
。
我不是结构化绑定的专家。可能有 &&
或 &
或 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)
);
}
基于 tuple_size
的 SFINAE 支持可能适合 to_struct
。
以上代码适用于所有类似元组的代码,例如 std::pair
、std::array
以及您自定义代码以支持结构化绑定的任何内容(tuple_size
和 get<I>
).
有趣的是,
std::array<int, 3> arr{1,2,3};
auto t = to_tuple<3>(arr);
和 returns 一个包含 3 个元素的元组,因为 to_tuple
基于结构化绑定,它使用类似元组的输入作为输入。
to_array
是这个家庭的另一种可能。
假设我有 struct
和 std::tuple
具有相同类型的布局:
struct MyStruct { int i; bool b; double d; }
using MyTuple = std::tuple<int,bool,double>;
有没有标准化的方法可以将一个转换为另一个?
P.S。我知道简单的内存复制可以解决问题,但它依赖于对齐和实现
Is there any standartized way to cast one to another?
没有办法 "cast" 一个到另一个。
最简单的方法可能是使用 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;
Demo.
您可以进一步将其包装在更高级别的宏或 "generator"(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;
}
I know that trivial memory copying can do the trick, but it is alignment and implementation dependent?
您提到 "trivial memory copy" 会起作用 - 仅用于复制单个成员。所以基本上,从 tuple
到 tuple
的整个结构的 memcpy
和反之亦然不会总是像你期望的那样表现(如果有的话); tuple
的内存布局未标准化。如果确实有效,则高度依赖于实现。
不幸的是,没有自动的方法来做到这一点,但另一种方法是使结构适应 Boost.Fusion 序列。您为每个新的 class.
一次性执行此操作#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-adapting"之后您可以这样做:
#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
是一个 half-backed 特征。这就像有 STD 容器而没有算法。幸运的是,我们有 #include <boost/fusion/adapted/std_tuple.hpp>
可以让我们做出惊人的事情。
完整代码:
通过包含 Boost.Fusion 中的 std_tuple.hpp
header 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)
,因为那会导致投反对票并且不可移植。
元组到 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;
}
我们可以使用结构化绑定将结构转换为元组,只需一些工作。
结构到元组非常尴尬。
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
的值。所以你必须在调用它时在 auto t = to_tuple<3>(my_struct);
中键入 3
。
我不是结构化绑定的专家。可能有 &&
或 &
或 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)
);
}
基于 tuple_size
的 SFINAE 支持可能适合 to_struct
。
以上代码适用于所有类似元组的代码,例如 std::pair
、std::array
以及您自定义代码以支持结构化绑定的任何内容(tuple_size
和 get<I>
).
有趣的是,
std::array<int, 3> arr{1,2,3};
auto t = to_tuple<3>(arr);
和 returns 一个包含 3 个元素的元组,因为 to_tuple
基于结构化绑定,它使用类似元组的输入作为输入。
to_array
是这个家庭的另一种可能。