gnac
gnac

Reputation: 1643

Store Templated data type in a struct

We are using a PubSub framework that uses template methods to register topics and identify as a publisher or subscriber. For example, to register a topic name in the system you'd call:

RegisterTopic<T>(std::string topicName);

I would like to be able to create a parameter list (or configuration list if you will) to identify the needed registration topics in use by a given application configuration using a simple struct like

struct RegistrationTopicType
{
    std::string name;
    int type;  // replace with some mechanism to store a data type
};

However I haven't found a working mechanism to replace the int in the struct above with something to store a data type.

I'm trying to do this so that I can maintain a list of topics and their types in the system:

std::vector<RegistrationTopicType> topicList = {
    {"topic1", MyClass},
    {"topic2", MyOtherClass}, // and on and on.
};

which I could use later do things like call the templated Registration method.

for (auto & topic : topicList) {
  RegisterTopic<topic.type>(topic.name);
}

I have experimented with templated struct where I tried to store the data type such as

template<typename T>
struct TemplatedRegistrationTopic
{
    using DataType       = T;
    std::string name;
};

But while I can implement instances of this struct,

TemplatedRegistrationTopic <float> topic{"floatTopic"};
TemplatedRegistrationTopic <MyClass> topic{"MyClassTopic"};

I can't seem to access the "DataType" variable as a datatype to use in the Registration method.

RegisterTopic<topic.DataType>(topic.name); // this fails.

I saw somewhere to use '::' but that also fails:

Register<topic::DataType>(topic.name); // this also fails.

Problems of storing a bunch of topics of different types in a common list aside, is what I'm trying to do (store a data type for later use in a templated method) even possible?

In the ol' days I might have created an enum list with an enum for of the possible types and then used that to select the actual type in a massive switch statement, but I don't want to have to maintain a mapping between all of the possible types and an enum in a giant switch statement which seems to defeat the purpose of using templates. eg I don't want to end up with this, but its effectively what I'm trying to do:

enum typelist { Type1, Type2 }; // one for each type that might be used as a topic

struct EnumRegistrationTopicType
{
    std::string name;
    typelist type;
};

std::vector<EnumRegistrationTopicType> enumTopicList = {
    {"topic1", Type1},
    {"topic2", Type2}, // and on and on.
};

for (auto & topic : enumTopicList) {
  switch (topic.type) {
  case typelist::Type1:
    RegisterTopic<MyClass1>(topic.name);
    break;
  case typelist::Type2:        
    RegisterTopic<MyClass2>(topic.name);
    break;
  }
}

Upvotes: 0

Views: 70

Answers (1)

n. m. could be an AI
n. m. could be an AI

Reputation: 120021

Let's do a classic OO design.

 struct RegistratorBase {
   virtual ~RegistratorBase() = default;
   virtual void doRegistration(const std::string& topic) const = 0;
 };

 template <typename T>
 struct Registrator {
    void registerTopic(const std::string& topic) const override {
        doRegistration<MyClass1>(topic);
    }
 };

 struct RegistrationTopicType {
     std::string name;
     std::unique_ptr<RegistratorBase> registrator;
 }

Now you can add these things to a vector

 std::vector<RegistrationTopicType> topicList {
     { "topic1", new Registrator<MyClass1> },
     { "topic2", new Registrator<MyClass2> },
 };     

and register everything

 for (const auto& topic: topicList) {
    topic.registrator->doRegistration(topic.name);
 }

Of course now one starts to wonder, why is RegistrationTopicType needed at all? Why not shove the name directly to the Registrator? OK let's try:

 struct RegistratorBase {
   virtual ~RegistratorBase() = default;
   // no need for other virtual members
 };

 template <typename T>
 struct Registrator {
    Registrator (const string& topic) {
       RegisteerTopic<T>(topic);
    }
 };

Nice! Now let's put this into a vector:

 std::vector<std::unique_ptr<RegistratorBase>> topicList {
    new Registrator<MyClass1>("topic1"),
    new Registrator<MyClass2>("topic2")
 };

Great! But why do we need a vector of these things? We construct the vector of registrators and never use it anymore. Why not just create a bunch of variables instead?

 Registrator<MyClass1> r1("topic1");
 Registrator<MyClass2> r2("topic2");

But that's not really different from a list of function calls:

 RegisterTopic<MyClass1>("topic1");     
 RegisterTopic<MyClass2>("topic2");

So variables are not needed either, nor are their classes. Put the calls somewhere and you are done. They are your configuration list.

We have successfully designed, simplified, and finally eliminated a totally redundant software component!

Perhaps some of the simplification steps will not be applicable to your case, in which case the component will not be redundant for you. But I have no way of knowing that.

Upvotes: 2

Related Questions