Boost Fusion:在编译时验证适应的struct成员排序

时间:2016-09-29 05:30:58

标签: c++ template-meta-programming boost-mpl static-assert boost-fusion

我正在使用BOOST_FUSION_ADAPT_STRUCT(),我需要检查所有成员是否已按正确顺序进行声明。所以首先我这样做了:

template <typename Sequence>
struct checker
{
    static void check()
    {
        typedef typename mpl::accumulate<Sequence, mpl::size_t<0>,
            mpl::plus<mpl::_1, mpl::sizeof_<mpl::_2>>>::type total_size;
        static_assert(sizeof(Sequence) == total_size::value, "omitted field?");
    }
};

这有效:

struct foo
{
    int x;
    float y;
    double z;
};
BOOST_FUSION_ADAPT_STRUCT(foo, x, y, z);
checker<foo>::check(); // fails if any field is missing

接下来我想确保顺序正确,因此例如上例中的(x, z, y)将无法编译。但到目前为止,我只想出了一个运行时解决方案(添加到check()):

        const Sequence* dummy = nullptr;
        ++dummy;
        boost::fusion::for_each(*dummy, struct_offset_checker());

使用这个仿函数:

struct struct_offset_checker
{
    mutable const void* _last = nullptr;

    template <typename Element>
    void operator()(const Element& element) const
    {
        if (&element <= _last)
            throw std::logic_error("struct member is declared in a different order");
        _last = &element;
    }
};

但我宁愿有一个编译时的解决方案。你能想到一个吗?

有趣的是,GCC实际上能够在编译时弄清楚何时会抛出异常,如果我有-Wsuggest-attribute=noreturn - 它告诉我什么时候调用check()的函数不会返回(由于logic_error)。

如果您想自己尝试,相关的标题是:

#include <stdexcept>
#include <boost/fusion/adapted.hpp>
#include <boost/mpl/accumulate.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/sizeof.hpp>
#include <boost/mpl/size_t.hpp>

2 个答案:

答案 0 :(得分:2)

为了执行编译时检查,您可以使用constexprstd::index_sequence的方式在自适应序列上进行迭代:

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/at.hpp>

struct foo
{
    char c;
    int x;
    float y;
    double z;
};


BOOST_FUSION_ADAPT_STRUCT(foo, x, c, y, z)

template <typename Sequence>
struct check_order
{
    template <std::size_t First, std::size_t Second>
    static constexpr bool internal()
    {
        constexpr Sequence* s = nullptr;
        const void* first = &boost::fusion::at_c<First>(*s);
        const void* second = &boost::fusion::at_c<Second>(*s);
        if (second <= first)
        {
            throw std::logic_error("struct member is declared in a different order");
        }
        return true;
    }

    template <std::size_t... Is>
    static constexpr bool run(std::index_sequence<Is...>)
    {
        int list[] = {(internal<Is,Is+1>(),0)...};
        (void)list;
        return true;
    }

    static constexpr void check()
    {
        constexpr std::size_t size = boost::fusion::result_of::size<Sequence>::type::value;
        static_assert(run(std::make_index_sequence<size-1>{}), "");
    }
};


int main()
{
    check_order<foo>::check();
}

根据需要,失败了:

main.cpp: In instantiation of 'static constexpr void check_order<Sequence>::check() [with Sequence = foo]':
main.cpp:49:23:   required from here
main.cpp:42:9: error: non-constant condition for static assertion
         static_assert(run(std::make_index_sequence<size-1>{}), "");
         ^~~~~~~~~~~~~
main.cpp:42:26:   in constexpr expansion of 'check_order<Sequence>::run<{0ul, 1ul, 2ul}>((std::make_index_sequence<3ul>{}, std::make_index_sequence<3ul>()))'
main.cpp:34:41:   in constexpr expansion of 'check_order<Sequence>::internal<0ul, 1ul>()'

live example

答案 1 :(得分:1)

使用m.s.的答案作为灵感,我使用Boost.MPL而不是std::make_index_sequence<>制定了一个解决方案,部分原因是因为我更习惯于这种风格:

typedef mpl::range_c<unsigned, 0, fusion::result_of::size<Sequence>::value - 1> IndicesWithoutLast;
mpl::for_each<IndicesWithoutLast>(struct_offset_checker<Sequence>());

使用这个仿函数:

template <typename Sequence>
struct struct_offset_checker
{
    template <typename Index>
    constexpr void operator()(Index)
    {
        typedef typename mpl::plus<Index, mpl::int_<1>>::type NextIndex;

        constexpr Sequence* dummy = nullptr;
        constexpr void* curr = &fusion::at<Index>(*dummy);
        constexpr void* next = &fusion::at<NextIndex>(*dummy);
        static_assert(curr < next, "fields are out of order");
    }
};