Assembly unit in logarithmic time

How to determine the arity of a population in the logarithmic (at least the base court) compilation time (strictly speaking, in the logarithmic number of instances)?

Currently, I can achieve the desired in linear time:

#include <type_traits> #include <utility> struct filler { template< typename type > operator type (); }; template< typename A, typename index_sequence = std::index_sequence<>, typename = void > struct aggregate_arity : index_sequence { }; template< typename A, std::size_t ...indices > struct aggregate_arity< A, std::index_sequence< indices... >, std::__void_t< decltype(A{(indices, std::declval< filler >())..., std::declval< filler >()}) > > : aggregate_arity< A, std::index_sequence< indices..., sizeof...(indices) > > { }; struct A0 {}; struct A1 { double x; }; struct A2 { int i; char c; }; struct C50 { template< typename ...Args, typename = std::enable_if_t< (sizeof...(Args) < 51) > > C50(Args &&...) { ; } }; static_assert(aggregate_arity< A0 >::size() == 0); static_assert(aggregate_arity< A1 >::size() == 1); static_assert(aggregate_arity< A2 >::size() == 2); static_assert(aggregate_arity< C50 >::size() == 50); 

Living example .

Please correct me if the term "arity" is bad.

I think this is possible in principle: firstly, you need to double the arity tests, starting with one, until SFINAE works (of course, gently), then use the division in half.

+3
source share
2 answers

Discussion

(The discussion is based on another answer that I will delete now.)

As in the original question, the following answer checks if it is possible to invoke an aggregate constructor with a given number of arguments. For aggregates, you can build a binary search on this template using the following properties from the standard:

8.5.1 (6):

The initialization list is poorly formed if the number of initializers-offers exceeds the number of elements or elements for initialization . [Example: char cv [4] = {a, s, d, f, 0}; // the error is poorly formed. - end example]

and

8.5.1 (7):

If there are fewer initializer elements in the list than there are members in the aggregate, then each member that is not explicitly initialized should be initialized from the default element initializer (9.2) or, if there is no default element initializer, from an empty list of initializers (8.5.4). [Example: struct S {int a; const char * b; int c; int d = b [a]; }; S ss = {1, "asdf"}; initializes ss.a with 1, ss.b with "asdf", ss.c with the value of an expression of the form int {} (that is, 0) and ss.d with the value ss.b [ss.a] (i.e. s) and in struct X {int i, j, k = 42; }; X a [] = {1, 2, 3, 4, 5, 6}; X b [2] = {{1, 2, 3}, {4, 5, 6}}; a and b have the same meaning example]

However, as you already meant by the question heading, binary search does not work with non-aggregates at all, firstly, because they usually are not called with less parameters than necessary, and then because non-aggregates can have explicit constructors, so the "convert to something" trick through a struct filler will not work.

Implementation

The first ingredient is the is_callable check from here :

 template<typename V, typename ... Args> struct is_constructible_impl { template<typename C> static constexpr auto test(int) -> decltype(C{std::declval<Args>() ...}, bool{}) { return true; } template<typename> static constexpr auto test(...) { return false; } static constexpr bool value = test<V>(int{}); using type = std::integral_constant<bool, value>; }; template<typename ... Args> using is_constructible = typename is_callable_impl<Args...>::type; 

Please note that this parameter can also be used with fewer parameters than necessary (unlike your check).

Next is a helper function that takes an integer argument and returns whether the aggregate is callable with the appropriate number of constructor arguments:

 template<typename A, size_t ... I> constexpr auto check_impl(std::index_sequence<I ...>) { return is_constructible<A, decltype(I, filler{}) ...>::value; } template<typename A, size_t N> constexpr auto check() { return check_impl<A>(std::make_index_sequence<N>{}); } 

And finally, binary search:

 template<typename A, size_t Low, size_t Up, size_t i = Low + (Up - Low)/2> struct binary_search : public std::conditional_t<check<A, i>() && !check<A,i+1>() , std::integral_constant<size_t, i> , std::conditional_t<check<A, i>() , binary_search<A, i, Up> , binary_search<A, Low, i> > > {}; 

Use it like

 int main() { static_assert(binary_search<A2,0,10>::value==2); } 

Live on coliru

+2
source

First, a little terminology: we can say that you are not so much looking for aggregate initialization as the maximum aggregate of initialization. For instance. a label with the name A2 can be an aggregate initialized from arguments 0, 1, and 2, so its maximum arity is 2.

Allows you to turn an "aggregate initialized from N arguments" into a dash (albeit with a shorter name):

 struct filler { template<typename type> operator type () const; }; template<typename Arg> void accept(Arg); template<typename Aggr, std::size_t... Indices, typename = decltype( accept<Aggr>({ (static_cast<void>(Indices), filler {})... }) )> void aggregate_arity_test(std::index_sequence<Indices...>); template<typename Aggr, int N, typename Sfinae = void> struct has_aggregate_arity: std::false_type {}; template<typename Aggr, int N> struct has_aggregate_arity<Aggr, N, std::void_t<decltype( aggregate_arity_test<Aggr>(std::make_index_sequence<N>()) )>> : std::true_type {}; 

(We use accept<Aggr>({ args... }) because it is the same as checking for Aggr aggr = { args... }; that is, initializing the copy list and what people have in when they talk about aggregate initialization. Aggr aggr { args.. }; is direct list initialization, but you can still check this if that is what you care about.)

Now we can find arity for which initialization fails in not too many duplicate duplicate instances (i.e. we will check with arty 0, then arity 1, arity 2, arity 4, arity 8, ..., arity 2 i ):

 template<typename Aggr, int Acc = 0> struct find_failing_init_fast: std::conditional_t< has_aggregate_arity<Aggr, Acc>::value, find_failing_init_fast<Aggr, Acc == 0 ? 1 : Acc * 2>, std::integral_constant<int, Acc> > {}; 

Now this is a binary search question inside [0, N) , where N is the arity for which initialization is not performed:

 // binary search invariant: // has_aggregate_arity<Aggr, Low> && !has_aggregate_arity<Aggr, High> template<typename Aggr, int Low, int High> struct max_aggregate_arity_impl : std::conditional_t< has_aggregate_arity<Aggr, midpoint(Low, High)>::value && !has_aggregate_arity<Aggr, midpoint(Low, High) + 1>::value, std::integral_constant<int, midpoint(Low, High)>, std::conditional< has_aggregate_arity<Aggr, midpoint(Low, High)>::value, max_aggregate_arity_impl<Aggr, midpoint(Low, High), High>, max_aggregate_arity_impl<Aggr, Low, midpoint(Low, High)> > >::type {}; // special case that 'errors' out (important for SFINAE purposes) // as the invariant obviously cannot be maintained template<typename Aggr> struct max_aggregate_arity_impl<Aggr, 0, 0> {}; template<typename Aggr> struct max_aggregate_arity : max_aggregate_arity_impl<Aggr, 0, find_failing_init_fast<Aggr>::value> {}; 

Live on coliru

+3
source

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


All Articles