Steve Lorimer
Steve Lorimer

Reputation: 28679

boost::fusion::map allows duplicate keys

According to the boost::fusion::map docs:

A map may contain at most one element for each key.

In practice, it is easy to violate this.

I am able to define the following type:

using map_type = fusion::map<
    fusion::pair<int, char>
  , fusion::pair<int, char>
  , fusion::pair<int, char>>;

and instantiate it with these duplicate keys:

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

Iterating over the map using fusion::for_each shows the data structure does indeed contain 3 pairs, and each of the keys is of type 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 {});

Output:

i=X
i=Y
i=Z

I would have expected a static_assert on key uniqueness, but this is obviously not the case.

Full working example: (on 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;
}

Due to comments below, here are some further details on what I'm actually trying to achieve.

The idea is to automatically generate FIX serialisation code.

A given field type can only exist once in any given FIX message - hence wanting the static_assert

Motivating example: (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;
}

Upvotes: 3

Views: 999

Answers (2)

SergeyA
SergeyA

Reputation: 62603

It is not an answer (OP already provided an answer), but a response to request to clarify some of my comments.

One way of achieving key uniqueness would be thrugh raw mpl usage. For example, taking FIX message as our domain, following piece of code should illustrate the idea. The code was not compiled and provided as generic illustration example only.

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

Now you have all the instrospection into execution report you want. You can easily serialize it with boost::mpl::for_each, or you can deserialze any message and get strongly-typed FixMessage.

I am not sure if you going to get a compilation error if you use the same type twice, but I am sure that you will only see the type once when iterating.

Upvotes: 1

Steve Lorimer
Steve Lorimer

Reputation: 28679

From the docs on associative containers:

... Keys are not checked for uniqueness.

As alluded to by Richard Hodges, this is likely by design

wouldn't that static_assert involve a geometric template expansion each time it was encountered?

Nonetheless, it is possible to use boost::mpl to reduce the sequence provided to the fusion::map into a unique sequence, and static_assert on the sequence lengths being the same.

First we create a struct which iterates over the list of types and creates a sequence of unique types

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

Then we change the definition of Map to use UniqueSeq::type to generate 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;
};

So given a list of fields, we can create a fusion::vector and a fusion::map with the result of UniqueSeq<Fields>, and assert the size of each is the same:

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

Passing duplicated fields now causes a compilation error:

static assertion failed: 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,
     ^

Full example on coliru

Upvotes: 2

Related Questions