1

In C++ 17 and above one can decompose a structure into its variables as so:

struct MyStruct
{
    int f1;
    int f2;
    int f3;
};
auto& [f1, f2, f3] = my_struct;

I am trying to do that in a variadic pack function as so:

template <class ... T, std::size_t...I>
constexpr std::size_t CountFields(std::index_sequence<I...>)
{
    T t;
    auto& [] = t;
    return 0;
}

But I am not entirely sure how to populate the space between the brackets to get the variables. The final goal is to use those variables to create a tuple (i.e I am trying to make a generic struct to tuple function). The closest I have found on this is this: struct to/from std::tuple conversion.

But I need to avoid BOOST for several reasons.

Makogan
  • 8,208
  • 7
  • 44
  • 112
  • [Yakk's solution](https://stackoverflow.com/a/38575501/2684539) doesn't use boost. [magic_get](https://github.com/apolukhin/magic_get) is included in boost, but doesn't use boost by itself. – Jarod42 Aug 17 '20 at 07:22

1 Answers1

1

sadly, it's clearly that there is no way to use a parameter pack in structured binding. and in the other hand, we can't bind the data members without structured binding.

but, if you give the amount of data members, you can bind it by a traditional way:

template<size_t>
struct structToTupleHelper;

template<>
struct structToTupleHelper<0>; // ISO C++17 does not allow a decomposition group to be empty.

template<>
struct structToTupleHelper<1>{
    template<typename X, size_t... Is>
    static auto convert(X&& x, std::index_sequence<Is...>){
        auto&& [a1] = std::forward<X>(x); // bound variables are always thought as lvalue.
        auto temp = std::forward_as_tuple(a1);
        return std::forward_as_tuple(std::get<Is>(temp)...);
    }
};

template<>
struct structToTupleHelper<2>{
    template<typename X, size_t... Is>
    static auto convert(X&& x, std::index_sequence<Is...>){
        auto&& [a1, a2] = std::forward<X>(x); // bound variables are always thought as lvalue.
        auto temp = std::forward_as_tuple(a1, a2);
        return std::forward_as_tuple(std::get<Is>(temp)...);
    }
};

// ...
// maybe 16 is enough?

template<typename X, size_t... Is>
auto structToTuple(X&& x, std::index_sequence<Is...> _1){
    return structToTupleHelper<sizeof...(Is)>::convert(std::forward<X>(x), _1);
}

template<size_t N, typename X, size_t... Is>
auto structToTuple(X&& x, std::index_sequence<Is...> _1){
    return structToTupleHelper<N>::convert(std::forward<X>(x), _1);
}

template<size_t N, typename X>
auto structToTuple(X&& x){
    return structToTupleHelper<N>::convert(std::forward<X>(x), std::make_index_sequence<N>());
}

and then you can use it such as:

int ii;
struct A{
    int& a;
} a{ii};
struct B{
    int a;
} b;

auto t1 = structToTuple<1>(A{ii}); // tuple<int&>, valid.
auto t2 = structToTuple<1>(a);     // tuple<int&>, valid.
auto t3 = structToTuple<1>(B{});   // tuple<int&>, invalid: a lvalue reference is bound to a temporary object.
auto t4 = structToTuple<1>(b);     // tuple<int&>, valid.
somefunc(structToTuple<1>(B{})); // valid. the temporary object is alive inside 'somefunc'.
structToTuple<1>(A{ii}) = std::tuple(1); // valid. assign 1 to 'ii'.
structToTuple<1>(a) = std::tuple(1);     // valid. assign 1 to 'ii'.
structToTuple<1>(B{}) = std::tuple(1);   // unexpected. assign 1 to the member of a temporary object.
structToTuple<1>(b) = std::tuple(1);     // valid. assign 1 to 'b.a'.

// struct C{
//     int a : 8;
// } c;
// auto t5 = structToTuple<1>(C{}); // invalid: bitfields can not be treated as non-const lvalue reference
// auto t6 = structToTuple<1>(c);   // invalid: bitfields can not be treated as non-const lvalue reference

maybe you think it's troublesome to do specializations. fortunately, we can use macro to simplify it: (it's exactly what BOOST always does.)

#define XXX_CONCAT_HELPER(a, b) a##b
#define XXX_CONCAT(a, b) XXX_CONCAT_HELPER(a, b)

#define XXX_COMMA ,
#define XXX_COMMA_FUNC(a) ,
#define XXX_EMPTY
#define XXX_EMPTY_FUNC(a)

#define XXX_REPEAT_0(func, join)
#define XXX_REPEAT_1(func, join) func(1)
#define XXX_REPEAT_2(func, join) XXX_REPEAT_1(func,join) join(2) func(2)
// ...
#define XXX_REPEAT_256(func, join) XXX_REPEAT_255(func,join) join(256) func(256)

#define XXX_REPEAT(func, times, join) XXX_CONCAT(XXX_REPEAT_,times)(func,join)

// macro is not allowed to be recursive, so we need another repeat function.
#define XXX_ALIAS_REPEAT_0(func, join)
#define XXX_ALIAS_REPEAT_1(func, join) func(1)
#define XXX_ALIAS_REPEAT_2(func, join) XXX_ALIAS_REPEAT_1(func,join) join(2) func(2)
// ...
#define XXX_ALIAS_REPEAT_256(func, join) XXX_ALIAS_REPEAT_255(func,join) join(256) func(256)

#define XXX_ALIAS_REPEAT(func, times, join) XXX_CONCAT(XXX_ALIAS_REPEAT_,times)(func,join)

#define STRUCT_TO_TUPLE_TOKEN_FUNC(n) XXX_CONCAT(a,n)

#define STRUCT_TO_TUPLE_FUNC(n) \
template<> \
struct structToTupleHelper<n>{ \
    template<typename X, size_t... Is> \
    static auto convert(X&& x, std::index_sequence<Is...>){ \
        auto&& [XXX_REPEAT(STRUCT_TO_TUPLE_TOKEN_FUNC,n,XXX_COMMA_FUNC)] = std::forward<X>(x); \
        auto temp = std::forward_as_tuple(XXX_REPEAT(STRUCT_TO_TUPLE_TOKEN_FUNC,n,XXX_COMMA_FUNC)); \
        return std::forward_as_tuple(std::get<Is>(temp)...); \
    } \
}; \


XXX_ALIAS_REPEAT(STRUCT_TO_TUPLE_FUNC,128,XXX_EMPTY_FUNC)

#undef STRUCT_TO_TUPLE_TOKEN_FUNC
#undef STRUCT_TO_TUPLE_FUNC
Jarod42
  • 203,559
  • 14
  • 181
  • 302
RedFog
  • 1,005
  • 4
  • 10
  • Why using `std::index_sequence` to recompose decomposition of `temp` instead of returning directly the tuple: [Demo](http://coliru.stacked-crooked.com/a/8bc34da97088f242) – Jarod42 Aug 17 '20 at 23:03
  • @Jarod42 the questioner provide a `std::index_sequence`, so I think this is what he wants. :) – RedFog Aug 18 '20 at 03:41
  • I think op uses that only to have expansion for the variable. – Jarod42 Aug 18 '20 at 06:21