对数时间内聚合的Arity

时间:2016-02-17 17:29:21

标签: c++ c++11 aggregate c++14 typetraits

如何在对数(至少是二)编译时定义聚合的arity(严格来说,以对数的实例化方式)?

目前我能做的是在线性时间内实现所需:

#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);

Live example

如果条款&#34; arity&#34;请纠正我。很穷。

我认为原则上是可能的:首先需要从1开始进行双重试验,直到SFINAE失败(当然,以柔和的方式),然后使用二分法。

2 个答案:

答案 0 :(得分:3)

首先是一些术语:我们可以争辩说,您不是在寻找聚合初始化arity,而是最大聚合初始化arity。例如。恰当命名的A2可以从0,1和2参数初始化聚合,因此其最大arity为2.

让我们把'从N个参数初始化的聚合'变成一个特征(虽然名字较短):

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 {};

(我们使用accept<Aggr>({ args... }),因为这与检查Aggr aggr = { args... };相同,即复制列表初始化以及人们在谈论聚合初始化时的想法。Aggr aggr { args.. };是直接的-list-initialization,但是如果那是你关心的,你仍然可以检查它。)

现在我们可以找到一个初始化失败的arity,没有太多的实例化迭代加倍(即我们将检查arity 0,然后arity 1,arity 2,arity 4,arity 8,...,arity 2 < SUP>我):

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>
> {};

现在这是[0, N)内的二进制搜索问题,其中N是初始化失败的arity:

// 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

答案 1 :(得分:2)

讨论

(讨论基于我的另一个答案,我现在将删除。)

与原始问题一样,以下答案检查是否可以使用给定数量的参数调用聚合的构造函数。对于聚合,可以使用标准中的以下属性对此模式进行二进制搜索:

  

8.5.1(6):

     

如果初始化子句的数量,则初始化列表格式错误   超过要初始化的成员或元素的数量。 [例如:   char cv [4] = {'a','s','d','f',0}; //错误是不正确的。 - 结束   例子]

  

8.5.1(7):

     

如果列表中的初始化程序子句数少于那么多   聚合中的成员,然后每个成员未明确初始化   应从其默认成员初始值设定项(9.2)初始化,或者,如果   从空的初始化列表中没有默认的成员初始值设定项   (8.5.4)。 [例子:struct S {int a; const char * b; int c; int d =   苯并[a]; }; S ss = {1,“asdf”};使用1,ss.b初始化ss.a   “asdf”,ss.c,其值为int {}形式的表达式(即   是,0)和ss.d,其值为ss.b [ss.a](即's'),并且   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和b具有相同的值 - 结束   例子]

但是,正如您已经在问题标题中暗示的那样,二进制搜索通常不适用于非聚合,首先是因为它们通常不能用较少的参数调用,而下一个是由于事实非聚合可以有explicit个构造函数,这样通过结构filler的“转换为任何东西”技巧将无效。

实施

第一个成分是来自hereis_callable支票:

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;

请注意,这个参数的数量也少于必要的参数(与检查不同)。

接下来是一个辅助函数,它接受一个整数参数并返回聚合是否可以使用相应数量的构造函数参数调用:

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>{});
}

最后是二元搜索:

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> >
                              >
{};

将其用作

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

Live on Coliru

相关问题