3

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.

  • Why is this?

  • How can I ensure that no one can instantiate a fusion::map with duplicate keys?

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;
}
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • Send a question to boost::fusion authors :) On any rate, I do believe boost::fusion is tad outdated now. – SergeyA Jan 20 '16 at 14:32
  • 1
    wouldn't that static_assert involve a geometric template expansion each time it was encountered? – Richard Hodges Jan 20 '16 at 14:40
  • @SergeyA can you suggest what to use instead of `fusion` if it is indeed a tad outdated? – Steve Lorimer Jan 20 '16 at 14:40
  • @SteveLorimer, `std::tuple` does wonders in simple cases :) If you want something more special, `inherit_lineraly` is usually good too. – SergeyA Jan 20 '16 at 14:41
  • 1
    @SergeyA How does that make Fusion outdated? Or how would you use `inherit_linearly` [sic] in this scenario? Perhaps you can answer using the alternative approach. – sehe Jan 20 '16 at 15:44
  • @Sehe, I am not exactly sure what author's scenario is - so I can't show how to use mpl algorithms. This is why I simply commented on it. If fusion is used for what I have seen it used - a class composition based on types - I can certainly show how to do this with 'raw' mpl. – SergeyA Jan 20 '16 at 15:50
  • 1
    Fusion does a lot more. What about your claim (it's interesting that you say fusion is outdated, and propose to implement things with it's much older peer, MPL) – sehe Jan 20 '16 at 16:06
  • @SergeyA I've added a motivating example as per sehe's request – Steve Lorimer Jan 20 '16 at 16:59
  • @sehe, from my experience, mpl is a much better designed library than fusion (which is based on MPL). It's like the Ford Focus 2000 and internal combustion engine - while Focus is outdated, combustion engine is (still) not. – SergeyA Jan 20 '16 at 17:13
  • As for the actual motivation, we have just did a major revamp of our FIX engine, no Fusion involved. I will try to see how much I can reveal without actually violating NDA, but no promises, sorry. – SergeyA Jan 20 '16 at 17:15
  • @SergeyA I appreciate the sentiment, even if you are unable to reveal the inner workings! Of course whatever you can reveal will be even more greatly appreciated! :-) – Steve Lorimer Jan 20 '16 at 17:17
  • @SergeyA if you are able to share any details on how you would solve this, I'd greatly appreciate that! Also, as requested by sehe, if you have any insight on how to use `inherit_linearly`, that would also be very interesting. TIA – Steve Lorimer Jan 21 '16 at 15:24
  • 1
    You've already answered your own question. Nevertheless, I will provide an illustrative example of generic use for composition in mpl. – SergeyA Jan 21 '16 at 15:39
  • @SergeyA Thanks - I only answered my own question to show how to achieve the actual thing I was asking for - however, I believe an answer which shows a different but superior approach is of equal if not better value – Steve Lorimer Jan 21 '16 at 15:44
  • I am not saying it's superior. I am just saying, that using mpl you will have uniqueness on the set. – SergeyA Jan 21 '16 at 16:02
  • @SergeyA understood, thanks – Steve Lorimer Jan 21 '16 at 17:28

2 Answers2

2

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

Community
  • 1
  • 1
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
1

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137