I've found an acceptable solution.
Some partially solutions (thanks to @davis-herring and @quxflux):
Conditions:
- We can't replace templates with scalars, because we have a lot of combinations. Even if we have two types.
- We can't instantiate templates during compilation of another code. I.e. this is a library used in Python.
- We don't want to make new instance manually always when new type will be added.
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).