Structured Link Width

Is it possible to determine how many variable names should be indicated in square brackets using the syntax of structured bindings to match the number of data elements on the simple right side of the struct ?

I want to make part of a shared library that uses structured bindings to decompose arbitrary classes into their components. At the moment there is no variable version of structured bindings (and, I think, it cannot be for the current proposed syntax), but first I decided to make a set of overloads of some function decompose() , which decomposes the struct parameter into a set of its components. decompose() must be overloaded with the number of data elements (which are struct ). Currently, constexpr if syntax can also be used to send this. But how can I imitate something like the sizeof... operator sizeof... for the above purposes? I can’t use the auto [a, b, c] syntax anywhere in the SFINAE constructs, because this is a decomposition declaration and AFAIK any declaration cannot be used inside decltype , nor can I use it for my purposes in the body of lambda functions, because lambda functions cannot also be used inside template arguments.

Of course, I want to have a built-in operator (with syntax like sizeof[] S / sizeof[](S) for class S ), but something like the following would also be acceptable:

 template< typename type, typename = void > struct sizeof_struct { }; template< typename type > struct sizeof_struct< type, std::void_t< decltype([] { auto && [p1] = std::declval< type >(); void(p1); }) > > : std::integral_constant< std::size_t, 1 > { }; template< typename type > struct sizeof_struct< type, std::void_t< decltype([] { auto && [p1, p2] = std::declval< type >(); void(p1); void(p2); }) > > : std::integral_constant< std::size_t, 2 > { }; ... etc up to some reasonable arity 

Maybe constexpr lambda will allow us to use them in template arguments. What do you think?

Is it possible using the following concepts?

+5
source share
2 answers
 struct two_elements { int x; double y; }; struct five_elements { std::string one; std::unique_ptr<int> two; int * three; char four; std::array<two_elements, 10> five; }; struct anything { template<class T> operator T()const; }; namespace details { template<class T, class Is, class=void> struct can_construct_with_N:std::false_type {}; template<class T, std::size_t...Is> struct can_construct_with_N<T, std::index_sequence<Is...>, std::void_t< decltype(T{(void(Is),anything{})...}) >>: std::true_type {}; } template<class T, std::size_t N> using can_construct_with_N=details::can_construct_with_N<T, std::make_index_sequence<N>>; namespace details { template<std::size_t Min, std::size_t Range, template<std::size_t N>class target> struct maximize: std::conditional_t< maximize<Min, Range/2, target>{} == (Min+Range/2)-1, maximize<Min+Range/2, (Range+1)/2, target>, maximize<Min, Range/2, target> > {}; template<std::size_t Min, template<std::size_t N>class target> struct maximize<Min, 1, target>: std::conditional_t< target<Min>{}, std::integral_constant<std::size_t,Min>, std::integral_constant<std::size_t,Min-1> > {}; template<std::size_t Min, template<std::size_t N>class target> struct maximize<Min, 0, target>: std::integral_constant<std::size_t,Min-1> {}; template<class T> struct construct_searcher { template<std::size_t N> using result = ::can_construct_with_N<T, N>; }; } template<class T, std::size_t Cap=20> using construct_airity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >; 

This leads to a binary search for the longest harmony T from 0 to 20. 20 is a constant, you can increase it, like you, with compilation time and memory.

Living example .

If the data in your structure cannot be built from an rvalue of its own type, this will not work in C ++ 14, but I believe that guanteed elision is found in C ++ 17 here (!)

Including this in structured bindings requires more than a bit of a heap of manual code. But as soon as you do this, you should be able to ask questions such as "what is the 3rd type of this struct ", etc.

If a struct can be decomposed into structured bindings without tuple_size driven material, then it determines the number of variables that it needs.

Unfortunately std::tuple_size not SFINAE friendly even in C ++ 17. But for types that use the tuple_size part, ADL-enable std::get also required.

Create a namespace with failure_tag get<std::size_t>(Ts const&...) , which using std::get . Use this to determine if they have redefined get<0> in the type ( !std::is_same< get_type<T,0>, failure_tag >{} ), and if so, go the tuple_element path to determine the speed. Put the resulting elements in std::tuple of decltype(get<Is>(x)) and return it.

If this fails, use the construct_airity above and use this to figure out how to use structured bindings for the type. I would probably send this to std::tie for uniformity.

Now we have tuple_it that takes any structured binding and converts it into a tuple of links or values. Now both paths converge, and your common code is easier!

+5
source

There is also a linear approach for searching for “aggregate arity” (although with the same rather strict restrictions as in the accepted answer):

 #include <type_traits> #include <utility> #include <tuple> struct filler { template< typename type > operator type && (); }; template< typename aggregate, typename index_sequence = std::index_sequence<>, typename = void > struct aggregate_arity : index_sequence { }; template< typename aggregate, std::size_t ...indices > struct aggregate_arity< aggregate, std::index_sequence< indices... >, std::void_t< decltype(aggregate{(indices, std::declval< filler >())..., std::declval< filler >()}) > > : aggregate_arity< aggregate, std::index_sequence< indices..., sizeof...(indices) > > { }; template< std::size_t index, typename type > constexpr decltype(auto) get(type & value) noexcept { constexpr std::size_t arity = aggregate_arity< std::remove_cv_t< type > >::size(); if constexpr (arity == 1) { auto & [p1] = value; if constexpr (index == 0) { return (p1); } else { return; } } else if constexpr (arity == 2) { auto & [p1, p2] = value; if constexpr (index == 0) { return (p1); } else if constexpr (index == 1) { return (p2); } else { return; } } else if constexpr (arity == 3) { auto & [p1, p2, p3] = value; if constexpr (index == 0) { return (p1); } else if constexpr (index == 1) { return (p2); } else if constexpr (index == 2) { return (p3); } else { return; } } else /* extend it by yourself for higher arities */ { return; } } // main.cpp #include <cstdlib> #include <cassert> namespace { using S = struct { int i; char c; bool b; }; S s{1, '2', true}; decltype(auto) i = get< 0 >(s); decltype(auto) c = get< 1 >(s); decltype(auto) b = get< 2 >(s); static_assert(std::is_same< decltype(i), int & >{}); static_assert(std::is_same< decltype(c), char & >{}); static_assert(std::is_same< decltype(b), bool & >{}); static_assert(&i == &s.i); static_assert(&c == &s.c); static_assert(&b == &s.b); } int main() { assert(i == 1); assert(c == '2'); assert(b == true); return EXIT_SUCCESS; } 

Currently, the get() argument cannot have a const top-level classifier (i.e. type can be && and & , but not const & and const && ), due to an error .

Living example .

+1
source

Source: https://habr.com/ru/post/1257444/


All Articles