boost::fusion::map允许重复的键。

3
根据boost::fusion::map文档:

每个键最多可以包含一个元素。

实际上,很容易违反这一点。
我可以定义以下类型:
using map_type = fusion::map<
    fusion::pair<int, char>
  , fusion::pair<int, char>
  , fusion::pair<int, char>>;

并使用这些重复的键实例化它:
map_type m(
    fusion::make_pair<int>('X')
  , fusion::make_pair<int>('Y')
  , fusion::make_pair<int>('Z'));

使用 fusion::for_each 迭代映射表,可以显示该数据结构确实包含3个键值对,每个键的类型都是 int

struct Foo
{
    template<typename Pair>
    void operator()(const Pair& p) const
    {
        std::cout << typeid(typename Pair::first_type).name() << "=" << p.second << '\n';
    }
};
fusion::for_each(m, Foo {});

输出:

i=X
i=Y
i=Z

我本来期望在键的唯一性上会有一个static_assert,但显然情况并非如此。

  • 为什么会这样?

  • 我该如何确保没有人可以使用重复的键实例化一个fusion::map

完整的工作示例:(在coliru上)

#include <boost/fusion/container.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <iostream>

namespace fusion = ::boost::fusion;

struct Foo
{
    template<typename Pair>
    void operator()(const Pair& p) const
    {
        std::cout << typeid(typename Pair::first_type).name() << "=" << p.second << '\n';
    }
};

int main()
{
    using map_type = fusion::map<
        fusion::pair<int, char>
      , fusion::pair<int, char>
      , fusion::pair<int, char>>;

    map_type m(
        fusion::make_pair<int>('X')
      , fusion::make_pair<int>('Y')
      , fusion::make_pair<int>('Z'));

    fusion::for_each(m, Foo {});
    return 0;
}

由于下面的评论,这里提供一些关于我实际想要实现的进一步细节。
这个想法是自动生成FIX序列化代码。
一个给定的字段类型只能在任何给定的FIX消息中存在一次 - 因此需要static_assert 激励性例子:(on coliru
#include <boost/fusion/container.hpp>
#include <boost/fusion/sequence.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/mpl/transform.hpp>
#include <iostream>

namespace fusion = ::boost::fusion;
namespace mpl    = ::boost::mpl;

template<class Field>
struct MakePair
{
    using type = typename fusion::result_of::make_pair<Field, typename Field::Type>::type;
};

template<class Fields>
struct Map
{
    using pair_sequence = typename mpl::transform<Fields, MakePair<mpl::_1>>::type;
    using type          = typename fusion::result_of::as_map<pair_sequence>::type;
};

///////////////////////////

template<typename... Fields>
class Message
{
public:
    template<class Field>
    void set(const typename Field::Type& val)
    {
        fusion::at_key<Field>(_fields) = val;
    }

    void serialise()
    {
        fusion::for_each(_fields, Serialiser {});
    }
private:

    struct Serialiser
    {
        template<typename Pair>
        void operator()(const Pair& pair) const
        {
            using Field = typename Pair::first_type;

            std::cout << Field::Tag << "=" << pair.second << "|";
        }
    };

    using FieldsVector = fusion::vector<Fields...>;
    using FieldsMap    = typename Map<FieldsVector>::type;

    FieldsMap _fields;

    static_assert(fusion::result_of::size<FieldsMap>::value == fusion::result_of::size<FieldsVector>::value,
            "message must be constructed from unique types"); // this assertion doesn't work
};

///////////////////////////

#define MSG_FIELD(NAME, TYPE, TAG)  \
    struct NAME                     \
    {                               \
        using Type = TYPE;          \
        static const int Tag = TAG; \
    };

MSG_FIELD(MsgType, char,   35)
MSG_FIELD(Qty,     int,    14)
MSG_FIELD(Price,   double, 44)

using Quote = Message<MsgType, Qty, Price>;

///////////////////////////

int main()
{
    Quote q;
    q.set<MsgType>('a');
    q.set<Qty>(5);
    q.set<Price>(1.23);

    q.serialise();
    return 0;
}

1
那个 static_assert 每次遇到时都会涉及到几何模板扩展吗? - Richard Hodges
@SergeyA,如果fusion确实有点过时了,你能建议使用什么替代品吗? - Steve Lorimer
1
@SergeyA,这怎么会使Fusion过时呢?或者在这种情况下你会如何使用inherit_linearly [sic]?也许你可以用另一种方法来回答。 - sehe
1
Fusion做了很多事情。那么你的说法呢(有趣的是,你说Fusion已经过时,并建议使用它更老的同行MPL来实现一些东西)? - sehe
1
您已经回答了自己的问题。尽管如此,我将提供一个在mpl中通用使用组合的说明性示例。 - SergeyA
显示剩余11条评论
2个回答

2

来自关于关联容器的文档

...键不会被检查是否唯一。

正如Richard Hodges所提到的,这很可能是有意为之的。

那个static_assert会不会在每次遇到时涉及到几何模板扩展?

尽管如此,我们仍然可以使用boost::mpl将提供给fusion::map的序列缩小为唯一序列,并static_assert序列长度相同。

首先,我们创建一个结构体,该结构体遍历类型列表并创建唯一类型序列。

// given a sequence, returns a new sequence with no duplicates
// equivalent to:
//  vector UniqueSeq(vector Seq)
//      vector newSeq = {}
//      set uniqueElems = {}
//      for (elem : Seq)
//          if (!uniqueElems.find(elem))
//              newSeq += elem
//              uniqueElems += elem
//      return newSeq
template<class Seq>
struct UniqueSeq
{
    using type = typename mpl::accumulate<
        Seq,
        mpl::pair<typename mpl::clear<Seq>::type, mpl::set0<> >,
        mpl::if_<
            mpl::contains<mpl::second<mpl::_1>, mpl::_2>,
            mpl::_1,
            mpl::pair<
                mpl::push_back<mpl::first<mpl::_1>, mpl::_2>,
                mpl::insert<mpl::second<mpl::_1>, mpl::_2>
            >
        >
    >::type::first;
};

然后我们更改Map的定义,使用UniqueSeq::type来生成pair_sequence

// given a sequence of fields, returns a fusion map which maps (Field -> Field's associate type)
template<class Fields>
struct Map
{
    using unique_fields = typename UniqueSeq<Fields>::type;
    using pair_sequence = typename mpl::transform<unique_fields, MakePair<mpl::_1>>::type;
    using type          = typename fusion::result_of::as_map<pair_sequence>::type;
};

所以,给定一个字段列表,我们可以使用UniqueSeq<Fields>的结果创建一个fusion::vector和一个fusion::map,并断言每个的大小相同:
using FieldsVector = fusion::vector<Fields...>;
using FieldsMap    = typename Map<FieldsVector>::type;

static_assert(fusion::result_of::size<FieldsMap>::value == fusion::result_of::size<FieldsVector>::value,
        "message must be constructed from unique types");

现在传递重复的字段会导致编译错误:

静态断言失败:消息必须由唯一类型构建

scratch/main.cpp: In instantiation of ‘class Message<Qty, Price, Qty>’:
scratch/main.cpp:129:23:   required from here
scratch/main.cpp:96:5: error: static assertion failed: message must be constructed from unique types
     static_assert(fusion::result_of::size<FieldsMap>::value == fusion::result_of::size<FieldsVector>::value,
     ^

在coliru上的完整示例


1

这不是一个答案(OP已经提供了答案),而是对澄清我的评论的请求的回应。

实现关键字唯一性的一种方法是通过原始mpl使用。例如,以FIX消息为我们的领域,以下代码应该说明这个想法。该代码未编译,仅作为通用示例提供。

template <class ValueType, int FieldTag>
struct FixField {
  using value_t = ValueType;
  static const short tag = FieldTag;
};

using CumQty = FixField<double, 14>;
using Price = FixField<double, 44>;

using inherit = boost::mpl::inherit<boost::mpl::placeholders::_1, boost::mpl::placeholders::_2>;

template <class list>
using inherit_linearly = boost::mpl::inherit_linearly<list, inherit>::type;

template <class Members> 
struct FixMessage : iherit_linearly<Members> {
  using members_t = Members;
  template <class T> T& get() { return static_cast<T&>(*this); } // const ver as well
};
struct ExecutionReport : public FixMessage<boost::mpl::set<CumQty, Price> > {
  static constexpr char const* name = "ExecutionReport";
};

现在,您已经拥有了所有关于执行报告的内省所需的信息。 您可以轻松使用boost :: mpl :: for_each对其进行序列化,或者可以反序列化任何消息并获得强类型的FixMessage。 我不确定如果您两次使用相同的类型是否会出现编译错误,但我确定在迭代时只会看到一次类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接