1

The following code illustrates the problem:

struct Data {
    int numFriends, numAcquaintances, numDates;
    // etc...
};

enum Country {USA, Canada, China}; // etc...
enum Personality {Serious, Lazy, Funny}; // etc...
enum Height {SuperShort, Short, Medium, Tall, SuperTall};

class Religion {};

class Person {
    std::map<Country, Data> countryStats;
    std::map<Personality, Data> personalityStats;
    std::map<Religion*, Data> religionStats;
    std::map<Height, Data> heightStats;  // And suppose there are 20 such similar data members

    // The problem:
    void insertNumFriends (Country country, int num) {countryStats[country].numFriends = num;}
    void insertNumFriends (Personality personality, int num) {personalityStats[personality].numFriends = num;}
    void insertNumFriends (Religion* religion, int num) {religionStats[religion].numFriends = num;}
    // and tons of other necessary methods (how to capture all of these using templates?);

Here is the (incomplete) solution that I've come up with, and even if the commented-out lines were to compile, it is still not a good method because of all the if-statements (in fact, it's just as long as the original method):

#include <iostream>
#include <map>
#include <typeinfo>
#include <typeindex>

struct Data {
    int numFriends, numAcquaintances, numDates;
    // etc...
};

enum Country {USA, Canada, China, France}; // etc...
enum Personality {Serious, Lazy, Funny}; // etc...
enum Height {SuperShort, Short, Medium, Tall, SuperTall};

class Religion {};

template <typename T>  // new struct here
struct Stats {
    std::map<T, Data> map;
};

class Person {
    Stats<Country> countryStats;
    Stats<Personality> personalityStats;
    Stats<Religion*> religionStats;
    Stats<Height> heightStats;  // And suppose there are 20 such similar data members
//  template <typename T> static std::map<std::type_index, Stats<T>> statsMap;  // illegal
  public:
    template <typename T>
    void insertNumFriends (const T& t, int num) {
        if (typeid(T) == typeid(Country)) {countryStats.map[t].numFriends = num;}
        // lines below will not compile, and it's a terrible method anyway:
//      else if (typeid(T) == typeid(Personality)) personalityStats.map[t].numFriends = num;
//      else if (typeid(T) == typeid(Religion)) religionStats.map[t].numFriends = num;
//      else if (typeid(T) == typeid(Height)) heightStats.map[t].numFriends = num;
    }
    void showAllStats() const {  // for testing only
        for (int COUNTRY = USA; COUNTRY <= France; COUNTRY++) {
            auto it = countryStats.map.find(static_cast<Country>(COUNTRY));
            if (it != countryStats.map.end())
            std::cout << "Country " << COUNTRY << ": " << it->second.numFriends << " friends" << std::endl;
        }
    // etc...
    }
};


int main() {
    Person bob;
    bob.insertNumFriends (USA, 5);
    bob.insertNumFriends (France, 2);
//  bob.insertNumFriends (Funny, 3); // won't compile because insertNumFriends<T> is not complete
    bob.showAllStats();  // USA: 5 friends, France: 2 friends
}

What's a better (working solution)? Acyclic Visitor Pattern or something like that? Note: Person has data members like name, age, countryOfOrigin, height, etc... too and does actually represent a person, and is not just an interface of containers (the container data members above are the person's "record").

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
prestokeys
  • 4,817
  • 3
  • 20
  • 43

2 Answers2

2

One possible solution: Emulate C++1y variable templates

Define a function holding the data, the map in this case:

template<typename T>
std::map<T,Data>& map()
{
    static std::map<T,Data> _map;

    return _map;
}

Now make only one generic insert function:

template<typename T>
void insert_data( const T& key , const Data& data )
{
    map<T>()[key] = data;
}
Manu343726
  • 13,969
  • 4
  • 40
  • 75
1

Yes, Manu's solution is so short and elegant! Just beautiful! Here is my code using his method, and finally I've found a need for the obscure pointer to data members:

#include <iostream>
#include <map>

struct Data {
    int numFriends, numAcquaintances, numDates;
};

enum Country {USA, Canada, France};
class Religion {} *Islam = new Religion;

class Person {
    private:
        template<typename T>
        std::map<T, Data>& dataMap() {static std::map<T, Data> map;  return map;}           public:
        template<typename T>
        void insert (const T& key, int Data::*dataPtr, int num) {
            dataMap<T>()[key].*dataPtr += num;  // pointer to data members finally useful!
        }
        void print() {  // for testing the results only
            std::cout << "Number of American friends: " << dataMap<Country>()[USA].numFriends << std::endl;
            std::cout << "Number of Islamic acquantances: " << dataMap<Religion*>()[Islam].numAcquaintances << std::endl;
        }
};

int main() {
    Person bob;
    bob.insert (USA, &Data::numFriends, 10);
    bob.insert (Islam, &Data::numAcquaintances, 20);
    bob.print();  // USA friends = 10, Islamic acquaintances = 20
}
prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • Update: Manu's answer has some problems. When I conduct the test with 2 people, they both share the same values when they should be separate. The static keyword solved one problem but added this new problem. What's the solution to that if we still want to go with Manu's idea? – prestokeys Jul 20 '14 at 14:01