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.
Я не уверен, что вы получите ошибку компиляции, если вы используете один и тот же тип дважды, но я уверен, что вы увидите этот тип только один раз при итерации.