How to instantiate template methods in a "cycle"? [duplicate]
Asked Answered
K

1

3

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?

Kktp answered 3/3, 2023 at 0:57 Comment(5)
Related: #39415812Aesthetics
Those aren’t explicit instantiations, but explicit specialization declarations.Cale
I can’t close a question with a bounty, but this is pretty clearly a duplicate; there is no non-macro solution, because explicit instantiations can’t themselves be the result of instantiating a template.Cale
Thank you. I found some interesting variants from your links, and I will write my own. Then I'll post it here.Kktp
I used macros to add the ugly stuff and parameterized the changing things. But please be aware, that GCC does NOT support partial specification outside of namespaces, so just DEFINING a template in *.h and IMPLEMENTING it in *.cpp in form of (partial) specification will NOT work for class members. For member fkts you have to fully implement the template inside the *.h ... THAT looks ugly! MSVC compiler accepts this ...Synchro
K
0

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

Kktp answered 3/4, 2023 at 22:32 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.