Orient Orient - 8 months ago 70
C++ Question

Structured bindings width

Is it possible to determine how many variable names should I to specify in square brackets using structured bindings syntax to match the number of data members of a plain right hand side


I want to make a part of generic library, which uses structured bindings to decompose arbitrary classes into its constituents. At the moment there is no variadic version of structured bindings (and, I think, cannot be for current syntax proposed), but my first thought is to make a set of overloadings of some function
, which performs decomposition of
parameter into a set of its constituents.
should be overloaded by number of parameter's (which is
) data members. Currently
constexpr if
syntax also can be used to dispatch this. But how can I emulate something similar to
operator for above purposes? I can't use
auto [a, b, c]
syntax somewhere in SFINAE constructions, because it is a decomposition declaration and AFAIK any declaration cannot be used inside
, also I cannot use it for my purposes in the body of lambda functions because lambda functions cannot be used inside template arguments too.

Surely I want to have builtin operator (with syntax like
sizeof[] S
class S
), but something like the following is also would 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

lambda will allow us to use them into template's arguments. What do you think?

Will it be possible with coming Concepts?

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{})...}) >>:
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:
      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>:
  template<std::size_t Min, template<std::size_t N>class target>
  struct maximize<Min, 0, target>:

  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 does a binary search for the longest construction airity of T from 0 to 20. 20 is a constant, you can increase it as you will, at compile-time and memory cost.

Live example.

If the data in your struct cannot be constructed from an rvalue of its own type, it won't work in C++14, but I believe guanteed elision occurs in C++17 here (!)

Turning this into structured bindings requires more than a bit of a pile of manual code. But once you have, you should be able to ask questions like "what is the 3rd type of this struct" and the like.

If a struct can be decomposed into structured bindings without the tuple_size stuff being done, the airity of it determines how many variables it needs.

Unfortunetally std::tuple_size is not SFINAE friendly even in C++17. But, types that use the tuple_size part also need to ADL-enable std::get.

Create a namespace with a failure_tag get<std::size_t>(Ts const&...) that using std::get. Use that to detect if they have overridden get<0> on the type (!std::is_same< get_type<T,0>, failure_tag >{}), and if so go down the tuple_element path to determine airity. Stuff the resulting elements into a std::tuple of decltype(get<Is>(x)) and return it.

If that fails, use the above construct_airity, and use that to figure out how to use structured bindings on the type. I'd probably then send that off into a std::tie, for uniformity.

We now have tuple_it which takes anything structured-binding-like and converts it to a tuple of references or values. Now both paths have converged, and your generic code is easier!