0

Let's say my class has an integer data member:

class MyClass {

public:
    MyClass();

private:
    int my_int;
}

Now upon construction, I want to receive a data type, a name and a value, all codified as strings:

public MyClass(string type, string name, string value) { ... }

And based on type and name, assign that value to my data member on construction. Something similar to:

public MyClass(string type, string name, string value)
{
    this->parse(type, name, value);
}

private parse(string type_, string name_, string value_)
{
    if (type_=="int")
    {
        int* int_ = getDataMember(name_);
        (*int_)=atoi(value_);
    }
    else ....
}

So now when i declared MyClass c("int","my_int","5"), c would be initialized with its my_int data member set to the value of 5.

The problem is I have no idea how to implement getDataMember(string name). But it would obviously return a pointer to its data member of the name "name".

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
roymcclure
  • 404
  • 3
  • 12
  • 2
    http://stackoverflow.com/questions/359237/why-does-c-not-have-reflection/359462#359462 – NickLamp Mar 01 '16 at 19:08
  • 1
    This isn't really how `C++` is intended to be used unless you are looking to create static types at compile time. In that case *templates* may do what you are looking for. – Galik Mar 01 '16 at 19:11
  • 5
    What are you trying to achieve? – NathanOliver Mar 01 '16 at 19:11
  • I can see this being done with `std::map`, but I'm with @NathanOliver . Probably a better way to do this if you let us in on the objective. – user4581301 Mar 01 '16 at 19:23
  • @NathanOliver I'm trying to create objects with a predefined set of values fetched from an xml file. – roymcclure Mar 01 '16 at 19:29
  • More explicitly, several classes will read an a priori undefined set of values and parse them to their own data members. – roymcclure Mar 01 '16 at 19:49
  • Be grateful that C++ doesn't have reflection. You should see how that feature wreaks havoc on certain Java architectures. – Christian Hackl Mar 01 '16 at 20:07
  • @ChristianHackl, compile-time reflection doesn't wreak any havoc, and it's addition to snatdard is actually being discussed in some circles. – SergeyA Mar 01 '16 at 20:31
  • @SergeyA: I was referring to run-time reflection. I don't know if the OP is looking for run-time reflection or for compile-time reflection. – Christian Hackl Mar 01 '16 at 20:33
  • @ChristianHackl, OP talks about hardcoding (in the comments to the answer) so I assume it's about compile-time. Run-time reflection should be banned. – SergeyA Mar 01 '16 at 20:36

1 Answers1

3

While this isn't how C++ is supposed to be used, it isn't entirely impossible.

#include <string>
#include <map>

class MyClass {

public:
    MyClass(const std::string& type, const std::string& name, const std::string& value);

private:
    void parse(const std::string& type_, const std::string& name_, const std::string& value_);
    template <typename T>
    T* getDataMember(const std::string& name);

    int my_int;
    double my_double;
    char my_char;
    std::map<std::string, char*> members; //Map of strings to member memory locations
};

MyClass::MyClass(const std::string& type, const std::string& name, const std::string& value)
{
    //List of members will have to be hardcoded
    members["my_int"] = reinterpret_cast<char*>(&my_int);
    members["my_double"] = reinterpret_cast<char*>(&my_double);
    members["my_char"] = reinterpret_cast<char*>(&my_char);
    this->parse(type, name, value);
}

template <typename T>
T* MyClass::getDataMember(const std::string& name) {
    return reinterpret_cast<T*>(members[name]); //Will need to handle invalid names
}

void MyClass::parse(const std::string& type_, const std::string& name_, const std::string& value_)
{
  if (type_=="int")
  {
    int* int_ = getDataMember<int>(name_);
    (*int_)=atoi(value_.c_str());
  }
}

int main(void) {

    MyClass c("int","my_int","5");

    return 0;
}

The idea is to keep a map to reference strings to member addresses. Accessing the map with the string will return the address of the member corresponding to that string. However, this map will have to be hardcoded when new members are introduced to the class. Also, the getDataMember function will have to handle cases where invalid names are passed into the class.

Inefficiencies

string comparisons are fundamentally slow. The comparisons occur when you are inserting members into the map, when you are going through your parse function trying to identify the type, and when you searching through the map for the correct key.

Possible ways to get rid of hardcoding

Suppose there is a way to fill the map without hardcoding. That would involve knowing what data members are present in the class. Since there does not seem to be such a functionality in C++, we would have to parse the file, or at least the part of the file that contains the class. However, we need this to be done before the file is compiled. This leaves us with 2 options:

  1. Do it at compile-time, which involves using preprocessor directives. Unfortunately, I have no idea of any way to utilize preprocessor directives to do so.
  2. Program an external executable that parses files in your project/workspace to identify the data members of the class and proceed to append the filling of the member map in the class's constructor(s). i.e. Automate the map filling process through an external executable. We can then ensure that this external executable is always run whenever the project is build through pre-build events.

Conclusion

I think it's best to either find another approach to the problem you are trying to solve, or use a whole other language because it seems C++ was not built for this.

Factoring in what your real problem is

I believe the solution you are looking for is called deserialization.

class MyClass {
public:
    void Deserialize(XMLParser xml);

private:
    int my_int;
    double my_double;
    char my_char;
};

void MyClass::Deserialize(XMLParser xml) {
    while(xml.ReadLine() != "MEMBERS"); //Advances the XML parser to the line that specifies the members
    //Proceed to deserialize the members in order
    //This requires the file to list the members in the correct order
    my_int = atoi(xml.ReadLine().c_str());
    my_double = atof(xml.ReadLine().c_str());
    my_char = atoi(xml.ReadLine().c_str());
}
Nard
  • 1,006
  • 7
  • 8
  • Thanks for the reply. I definitely wanted to get rid of the hardcoding. – roymcclure Mar 01 '16 at 19:30
  • We've got such stuff in production for years now. Well, using enums instead of strings actually and require to have explicitly declared binding (and check that at compile time). But basically that works well, yes. – πάντα ῥεῖ Mar 01 '16 at 19:30
  • @roymcclure _"I definitely wanted to get rid of the hardcoding."_ You can't really. Doing it right, you should introduce explicit bindings and compile time checks. – πάντα ῥεῖ Mar 01 '16 at 19:32
  • There is a way to get rid of hardcoding by creating your types specifically with serialization in mind. – SergeyA Mar 01 '16 at 20:32
  • @SergeyA would you please elaborate? How would I serialize a set of values to an instantiated object? – roymcclure Mar 02 '16 at 10:32
  • @roymcclure, for example, you might represent your class as a tuple with unique classess - where every field is of unique type, which has a *real* type and a static name. For example, if you have two double fields d1 and d2, you will create `class d1` (with name returning d1), `class d2` and put those two classes into tuple. – SergeyA Mar 02 '16 at 14:23