I have a class with several template methods, i.e.:
class MessageEndpoint
{
public:
using SupportedMessages = boost::mp11::mp_list<messaging::MessageType1, messaging::MessageType2, messaging::MessageTypeN>;
public:
virtual ~MessageEndpoint();
public:
template <typename MessageType>
UID subscribe(const UID &publisher_uid, std::function<void(const MessageType &)> callback);
template <typename MessageType>
void send_message(const MessageType &message);
template <typename MessageType>
void send_message(MessageType &&message);
}
These methods must be preinstantiated for the several types (in the SupportedMessages
list).
Of course, I can do something like this for every class in the list:
// Instantiation.
template <>
UID subscribe<messaging::MessageType1>(const UID &publisher_uid, std::function<void(const messaging::SpikeMessage &)> callback);
template <>
void MessageEndpoint::send_message<messaging::MessageType1>(messaging::MessageType1 &&message);
template <>
void MessageEndpoint::send_message<messaging::MessageType1>(const messaging::MessageType1 &message);
But this is long and ugly. How can I do something, like this:
template_for<MessageEndpoint::SupportedMessages, T,
{
template <>
UID subscribe<T>(const UID &publisher_uid, std::function<void(const T &)> callback);
template <>
void MessageEndpoint::send_message<T>(T &&message);
template <>
void MessageEndpoint::send_message<T>(const T &message);
}>
Is it possible? How can I do this with Boost::MP11?
I've found an acceptable solution.
Some partially solutions (thanks to @davis-herring and @quxflux):
Conditions:
All information about instantiation must be known at compile time. There is no C++ language construct for creating an "instantiation cycle".
So there is only one way to solve this problem: use a preprocessor. Convenient way is to use Boost.Preprocessor library.
I'll show simplified example: Entity-Relation base implementation. This example probably won't compile, but the real code it's based on, works.
namespace my_code
{
// There are comma separated types.
// I.e. declared somewhere in the type traits library.
// These will be used by preprocessor macroses.
// MUST be declared without parentheses.
#define ALL_ENTITIES EntityType1, EntityType2, EntityTypeN
// MP11 list creation: for example, how to work with types lists.
using AllEntites = boost::mp11::mp_list<ALL_ENTITIES>;
#define ALL_RELATIONS RelationOneToMany, RelationOneToOne
using AllRelations = boost::mp11::mp_list<ALL_RELATIONS>;
class ER
{
public:
// Some metaprogramming stuff.
using AllEntityContainers = boost::mp11::mp_transform<EntityContainer, AllEntites>;
using AllRelationContainers = boost::mp11::mp_transform<RelationContainer, AllRelations>;
using AllEntityVariants = boost::mp11::mp_rename<AllEntityContainers, std::variant>;
using AllRelationVariants = boost::mp11::mp_rename<AllRelationContainers, std::variant>;
public:
using EntityContainer = std::vector<AllEntityVariants>;
using RelationContainer = std::vector<AllRelationVariants>;
public:
// Templates must be instantiated explicitly.
template <typename EntityType>
void add_entity(EntityType &&entity);
template <typename EntityType>
EntityType &get_entity(const UID &entity_uid);
template <typename EntityType>
const EntityType &get_entity(const UID &entity_uid) const;
public:
// Another templates group must be instantiated explicitly.
template <typename RelationType>
void add_relation(RelationType &&relation);
template <typename RelationType>
RelationType &get_relation(const UID &relation_uid);
template <typename RelationType>
const RelationType &get_relation(UID &relation_uid) const;
private:
template <typename T, typename VT>
typename std::vector<VT>::iterator find_elem(const knp::core::UID &uid, std::vector<VT> &container);
private:
EntityContainer entities_;
RelationContainer relations_;
};
} // namespace my_code.
Implementation:
namespace my_code
{
// This template used in another template methods and will be instantiated automatically.
template <typename T, typename VT>
typename std::vector<VT>::iterator ER::find_elem(const UID &uid, std::vector<VT> &container)
{
auto result = std::find_if(
container.begin(), container.end(),
[&uid](VT &p_variant) -> bool
{
constexpr auto type_n = boost::mp11::mp_find<VT, T>();
if (p_variant.index() != type_n) return false;
return uid == (std::get<type_n>(p_variant)).get_uid();
});
return result;
}
// Must be instantiated explicitly.
template <typename EntityType>
void ER::add_entity(EntityType &&entity)
{
entities_.emplace_back(ER::AllEntitiesVariants(entity));
}
// Must be instantiated explicitly.
template <typename EntityType>
EntityType &ER::get_entity(const UID &entity_uid)
{
auto r = find_elem<EntityType, AllEntityVariants>(entity_uid, entities_);
if (r != entities_.end()) return std::get<EntityType>(*r);
throw std::runtime_error("Can't find entity!");
}
// Must be instantiated explicitly.
template <typename EntityType>
const EntityType &ER::get_entity(const UID &entity_uid) const
{
return const_cast<ER*>(this)->get_entity<EntityType>(entity_uid);
}
// Must be instantiated explicitly.
void ER::add_relation(ER::AllRelationVariants &&relation)
{
relations_.emplace_back(relation);
}
// Must be instantiated explicitly.
template <typename RelationType>
void ER::add_relation(RelationType &&relation)
{
add_relation(ER::AllRelationVariants(relation));
}
// Must be instantiated explicitly.
template <typename RelationType>
RelationType &ER::get_relation(const UID &relation_uid)
{
auto r = find_elem<RelationType, AllRelationVariants>(relation_uid, relations_);
if (r != relations_.end()) return std::get<RelationType>(*r);
throw std::runtime_error("Can't find relation!");
}
// Must be instantiated explicitly.
template <typename RelationType>
const RelationType &ER::get_relation(const UID &relation_uid) const
{
return const_cast<ER *>(this)->get_relation<RelationType>(relation_uid);
}
// Entity methods instantiation macro, which will be called in cycle
// by preprocessor.
#define INSTANCE_ENTITY_FUNCTIONS(n, template_for_instance, neuron_type) \
template void ER::add_entity<Entity<entity_type>>(Entity<entity_type> &&); \
template Entity<entity_type> &ER::get_entity<Entity<entity_type>>(const UID &); \
template const Entity<entity_type> &ER::get_entity<Entity<entity_type>>(const knp::core::UID &) const;
// Relation methods instantiation macro, which will be called in cycle
// by preprocessor.
#define INSTANCE_RELATION_FUNCTIONS(n, template_for_instance, relation_type) \
template void ER::add_relation<Relation<relation_type>>(Relation<relation_type> &&); \
template Relation<relation_type> &ER::get_relation<Relation<relation_type>>(const UID &); \
template const Relation<relation_type> &ER::get_relation<Relation<relation_type>>(const UID &) const;
// Entities instantiation cycle.
BOOST_PP_SEQ_FOR_EACH(INSTANCE_ENTITY_FUNCTIONS, "", BOOST_PP_VARIADIC_TO_SEQ(ALL_ENTITIES))
// Relations instantiation cycle.
BOOST_PP_SEQ_FOR_EACH(INSTANCE_RELATION_FUNCTIONS, "", BOOST_PP_VARIADIC_TO_SEQ(ALL_RELATIONS))
} // namespace my_code
This code is enough for me, but if somebody wants, he can make more complicated stuff, using this "technique".
For example, BOOST_PP_SEQ_FOR_EACH_PRODUCT
can be used to make all combinations of several classes list (some problems must will be solved, i.e. equal classes combinations instantiation several times, but this is possible).