Structured bindings width

不问归期 提交于 2019-11-29 07:14:24
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 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!

Also there is a linear approach to find the "aggregate arity" (though, also under the same strict enough circumsances, 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 argument of get() cannot have const top level type qualifier (i.e. type can be && and &, but not const & and const &&), due to the bug.

Live example.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!