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!