Search code examples
c++templatestemplate-meta-programmingtemplate-specializationboost-mp11

How to instantiate template methods in a "cycle"?


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?


Solution

  • 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).