boost::fusion::map позволяет дублировать ключи

Согласно бусту:: fusion:: map docs:

Карта может содержать не более одного элемента для каждого ключа.

На практике это легко нарушить.

Я могу определить следующий тип:

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 с дубликатами ключей?

Полный рабочий пример: ( по колиру)

#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

Мотивирующий пример: ( по колиру)

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

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

Таким образом, учитывая список полей, мы можем создать fusion::vector и fusion::map с результатом UniqueSeq<Fields>и утверждать, что размер каждого одинаков:

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,
     ^

Полный пример по колиру

Это не ответ (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.

Я не уверен, что вы получите ошибку компиляции, если вы используете один и тот же тип дважды, но я уверен, что вы увидите этот тип только один раз при итерации.

Другие вопросы по тегам