Structured binding to tuple

Just got into some C++17. Anyone know a good way to make a struct into a tuple? Best I got ATM is this pattern (code is bit rough)...

https://wandbox.org/permlink/ijBWSNvaZegY0yKS

...Could just generate 100 of these or something. But want some sort of variadic solution.
Last edited on
Not opening 3rd party links (actually, they are blocked here, I can get around that, but I am not risking a visit from security over your code).

So a quick thought process instead...
cant you just add to your existing struct an overloaded assignment operator that morphs what you have in yours into the tuple?

then you can have this sort of thing

tuple t = mystruct;

and you can also add a tuple method to your struct:
mystruct.totuple().accessasifatuplehere //totuple's return is a tuple, so you can just use it as one from the function call
or you can do some form of polymorph to make the above happen

I just want it automatic. No need to write any assignment operators etc. Here is that code sample I had. It's good enough, but want something variadic otherwise need to repeat SFINAE pattern N times. The pattern is the to_tuple function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <tuple>
#include <type_traits>
#include <vector>

using namespace std;

template<class T, typename... Args>
decltype(void(T{ std::declval<Args>()... }), std::true_type())
test_is_braces_constructible(int);

template<class T, typename... Args>
std::false_type
test_is_braces_constructible(...);

template<class T, typename... Args>
struct is_braces_constructible
{
    static constexpr bool value = std::is_same_v<decltype(test_is_braces_constructible<T, Args...>(0)), std::true_type>;
};

struct any_type {
    template<class T>
    constexpr operator T();// non explicit
};

template<typename T, typename... Args>
struct fits_brace_init_exactly
{
    static constexpr bool value = is_braces_constructible<T, Args...>::value && !is_braces_constructible<T, Args..., any_type>::value;
};

enum class enabler_t {};

template<typename T>
using EnableIf = typename std::enable_if<T::value, enabler_t>::type;

template<typename T>
static auto to_tuple(T& object, EnableIf< fits_brace_init_exactly<T, any_type, any_type, any_type, any_type> > = {}) noexcept
{
    auto&[p1, p2, p3, p4] = object;
    return std::forward_as_tuple(p1, p2, p3, p4);
}

template<typename T>
static auto to_tuple(T& object, EnableIf< fits_brace_init_exactly<T, any_type, any_type, any_type, any_type, any_type> > = {}) noexcept
{
    auto&[p1, p2, p3, p4, p5] = object;
    return std::forward_as_tuple(p1, p2, p3, p4, p5);
}

template<typename T>
static auto to_tuple(T& object, EnableIf< fits_brace_init_exactly<T, any_type, any_type, any_type, any_type, any_type, any_type> > = {}) noexcept
{
    auto&[p1, p2, p3, p4, p5, p6] = object;
    return std::forward_as_tuple(p1, p2, p3, p4, p5, p6);
}

template<typename LHS, typename RHS, typename F>
static auto memberwise_operation(LHS& lhs, RHS& rhs, const F& func)
{
    auto lhs_tuple = to_tuple(lhs);
    auto rhs_tuple = to_tuple(rhs);
    return func(lhs_tuple, rhs_tuple);
}

int main()
{
    struct Obj
    {
        double one;
        int two;
        char three;
        std::string four;
        const float five;
        std::vector<int> six;
    };

    auto test_obj = Obj{ 1, 1, 1, "whatever", 1,{ 1 } };

    {
        auto rhs = test_obj;
        bool less_than = memberwise_operation(test_obj, rhs, [](auto&lhs, auto&rhs) {return lhs < rhs; });
        bool greater_than = memberwise_operation(test_obj, rhs, [](auto&lhs, auto&rhs) {return lhs > rhs; });
        cout << less_than << ' ' <<greater_than<< endl;
        rhs.four = "zzzzzzzz";
        less_than = memberwise_operation(test_obj, rhs, [](auto&lhs, auto&rhs) {return lhs < rhs; });
        greater_than = memberwise_operation(test_obj, rhs, [](auto&lhs, auto&rhs) {return lhs > rhs; });
        cout << less_than << ' ' <<greater_than<< endl;
    }
    
    return 0;
}
Last edited on
Hi,

This sounds like an XY problem, there is problem X, already a solution Y, but there may be better solutions if only we knew what X was.

As I understand it at the moment you have 100 different structs which you would like to compare, and need a solution where you don't have to write operators for each one.

At the moment you have a solution that works for structs of the same type with 4 , 5 or 6 members.

How many members do these 100 structs have? It seems to me the current solution will work as long as the 2 structs have the same number of members, as in one could compare 2 of these:

1
2
3
4
5
6
struct Actor {
   Fred one;
   Ginger two;
   Chaplin three;
   Jackman four;
};


So I am guessing you might not have as many types compare as first thought. I could be wrong, it would be good to see what you are really doing :+)

Also, the memberwise_operation uses various templates to do a lot of checking of types and numbers thereof, it seems unnecessary when using < with a tuple, it returns false if things don't compare. Although it would be useful if not using an operator built in to std::tuple.




Going back to the current solution:

As I see it, at the moment all those arguments of any_type are there to prove that the all the member types are compatible. Maybe there is a way to exploit that to make a tuple out of any sized struct.

I am trying to have a go at the moment, will get back to you if I figure anything out :+)
So I am guessing you might not have as many types compare as first thought. I could be wrong, it would be good to see what you are really doing :+)


Hey. This is mostly just some experimental code. But yeah generating comparison operators is one possible use of this. Another one is something similar to https://wandbox.org/permlink/DKGpne8ZfymeqXyr . Once a struct is made into a tuple; quite a few compile-time reflection options become available.

Last edited on
Hi,

So this is looking to be out of my depth, somehow I guess there is a way to send fits_brace_init_exactly a parameter pack (it is already defined to accept one), and make the same parameter pack available to std::forward_as_tuple

In the link you provided, I noticed there are things like CanDivideByInt, CanCout , i wonder if something like constraints and concepts could be used there.

http://en.cppreference.com/w/cpp/language/constraints

Just throwing ideas around, I don't really have the knowledge to implement them :+)

Maybe this is a job for someone of the calibre of JLBorges
It could be that it's just not possible. I did just find https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/CcOGSKEoQa0 on the internet; which seems to cover this topic. The idea would mean to_tuple would be something like:

1
2
3
4
5
6
template<typename T>
static auto to_tuple(T& object)
{
    auto&[...param_pack] = object;
    return std::forward_as_tuple(param_pack...);
}
Last edited on
The problem as stated reminds me of BOOST_FUSION_ADAPT_STRUCT:
http://www.boost.org/doc/libs/1_66_0/libs/fusion/doc/html/fusion/adapted/adapt_struct.html

Which requires you to annotate the type of the structure's members. It's not so interesting since it just generates some traits classes. (Looking at the generated code makes it much easier to understand: see http://coliru.stacked-crooked.com/a/7de9592f911b7779 .)

Given the limitations of the destructuring assignments (which can't generate parameter packs), I thought about inspecting only part of the object at once, maybe by splitting the structure into a head and tail, like a old-style typelist or a cons pair. But if we knew how to do that this would be a solved problem.
Last edited on
@elohssa

Just wondering where you obtained the original code?

I know intuitively that fits_brace_init_exactly somehow connects the any_type arguments with the p1 to p6 variables in the structured binding, but I am not sure how that works exactly. Was there any explanation that came with the code?

Cheers :+)
Hey

any_type here is just used for SFINAE purposes. fits_brace_init_exactly<T,any_type[0]...any_type[N-1]>::value is true if a type T can be brace{} constructed using exactly N arguments.
Last edited on
I have found a way to improve it a little bit. Gets rid of half of the repeating pattern.

https://wandbox.org/permlink/xINSgJ5PC9BMXxdd
any_type here is just used for SFINAE purposes. fits_brace_init_exactly<T,any_type[0]...any_type[N-1]>::value is true if a type T can be brace{} constructed using exactly N arguments.


Cheers, thanks for that.

I had intuitively gathered that was what was happening, but how does any_type[0] connect to the p1 variable? Or is this premise incorrect?

I am guessing this is more complicated that just counting the number of arguments, otherwise sizeof... might have been used? Or is all this SFINAE just to select between 4, 5 or 6 arguments?

Is it because the template parameter T on line 23 in your original code, connects to the primary template parameter T on line 38, meaning that T is an T& object?

I should buy a book about this, I guess TMP is pretty tricky unless one builds knowledge incrementally :+)

Sorry to ask these questions, you are probably a busy person. But there are plenty of other experts around too :+)
Or is all this SFINAE just to select between 4, 5 or 6 arguments?

Yes, that's all it's for.

The auto&[p1, p2, p3, p4, p5, p6] = object; pattern is just the structured binding syntax introduced in c++17.

http://en.cppreference.com/w/cpp/language/structured_binding

Unfortunately it seems to have no variadic version. Hence the need for many to_tuple functions to select based on the number of arguments.
Last edited on
Ok cheers :+)

That clears that up.

I was aware of the structured binding, and had independently thought of the structured binding with parameter pack you presented earlier, but removed my mention of it because it wasn't possible having read the cppreference page.

If C++ were going to provide reflection (even only the inspection part), I wonder how they would go about doing it?

If C++ were going to provide reflection (even only the inspection part), I wonder how they would go about doing it?


Well the reflection code in my other example does work (https://wandbox.org/permlink/xINSgJ5PC9BMXxdd ); for both inspection and modifying. Not the same as .NET reflection of course in that member names are not accessible. It does have some performance advantages though.
Last edited on
> If C++ were going to provide reflection (even only the inspection part), I wonder how they would go about doing it?

The initial Reflection TS is probably going to be based on the static reflection proposal.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0578r1.html

most likely, with the addition of static reflection for functions
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0670r0.html#references
Last edited on
I found this:

http://jackieokay.com/2017/04/13/reflection1.html

There is the magic_get library. It still uses structured bindings (internally) and resorts to a python script to do generalized specialisations, and there are some limitations:

https://github.com/apolukhin/magic_get

There is also a description of using clang to inspect the AST.

As well:

https://github.com/bytemaster/boost_reflect

And:

https://bytemaster.github.io/boost_reflect/index.html
magic_get looks pretty neat! It also offers recursive member iteration via boost::pfr::flat_for_each_field. Combine this with C++17 if constexpr and basically everything in my example is nicely do-able.
Last edited on
Topic archived. No new replies allowed.