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?

A.N.
  • 278
  • 2
  • 13
  • 1
    Related: https://stackoverflow.com/questions/39415312/specialize-many-templates-for-a-set-of-types – Quxflux Mar 03 '23 at 05:43
  • 1
    Those aren’t explicit instantiations, but explicit specialization declarations. – Davis Herring Mar 07 '23 at 04:14
  • 5
    I can’t close a question with a bounty, but this is pretty clearly a [duplicate](https://stackoverflow.com/q/50338955/8586227); there is no non-macro solution, because explicit instantiations can’t themselves be the result of instantiating a template. – Davis Herring Mar 07 '23 at 06:25
  • Thank you. I found some interesting variants from your links, and I will write my own. Then I'll post it here. – A.N. Mar 07 '23 at 12:25
  • 1
    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 ... – Synopsis Mar 08 '23 at 13:09

1 Answers1

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

A.N.
  • 278
  • 2
  • 13