5

Problem: I want disparate sections of my code to be able to access a common collection that stores objects of different types in such a way that the type of each object is known and, crucially, retrieval from the collection should be type checked at compile time. (I realise this is close to questions asked before, but please read on, this is somewhat more specific.)

To give a concrete example, I would like something that does the following:

// Stuff that can go in the collection:
enum Key { NUM_APPLES /* (unsigned int) */, APPLE_SIZE /* (double) */ }
map<Key, Something> collection;

unsigned int * nApples = collection.find(NUM_APPLES);
int * appleSize = collection.find(APPLE_SIZE); // COMPILATION ERROR - WRONG TYPE

My solution: So far I have devised the following solution using boost::any:

The key:

using namespace std;
using namespace boost::any;

struct KeySupertype
{
protected:
    // Can't create an instance of this type
    KeySupertype() {}
private:
    // Can't copy
    KeySupertype& operator = (const KeySupertype& other) {}
    KeySupertype(const KeySupertype& other) {}
};

template <typename Type>
struct Key : public KeySupertype
{
public:
    Key() {}
};

The collection:

class PropertiesMap
{
public:
    template<typename T>
    T * find(Key<T> & key);

    /* Skipping erase, insert and other methods for brevity. */

private:
    map<const KeySupertype *, any> myAnyMap;
};

template <typename T>
T * PropertiesMap::find(Key<T> & key)
{
    const map<const KeySupertype *, any>::iterator it = myAnyMap.find(&key);

    if(it == myAnyMap.end())
        return NULL;

    return any_cast<T>(&it->second);
}

Usage:

static const Key<unsigned int> NUM_APPLES;
static const Key<double> APPLE_SIZE;

PropertiesMap collection;

/* ...insert num apples and apple size into collection ...*/

unsigned int * const nApples = collection.find(NUM_APPLES);
int * const nApples = collection.find(NUM_APPLES); // COMPILATION ERROR

This way type information is encoded with each Key according to its template parameter so the type will be enforced when interacting with the collection.

Questions:

1) Is this a reasonable way to achieve my goal?

2) A point of nastyness is that the collection uses the address of Key objects as the internal std::map key. Is there a way around this? Or at least a way to mitigate misuse? I've tried using a unique int in each Key that was generated from a static int (and making the std::map key type an int), but I'd like to avoid statics if possible for threading reasons.

3) To avoid using boost::any would it be reasonable to have the std::map be of type <const KeySupertype *, void *> and use a static_cast<T> instead of any_cast?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Martin
  • 176
  • 3
  • 8
  • 6
    Your code is cunning. But it raises the obvious question; why not just use a class? (`class Stuff { unsigned int num_apples; double apple_size; }`). I can't see how a map helps you here. – Oliver Charlesworth Mar 19 '12 at 18:35
  • I agree with Oli. The overhead included in a class and calling its members and functions is pretty negligable. It's also more readable and easier to modify in the future. – MintyAnt Mar 19 '12 at 18:40
  • 1
    Why do you want to store all these disparate types in a single container? You're doing a bunch of checking at compile time to make sure you get the right type back out, something that could be easily attained by just using a couple different containers (since you obviously already know what type you're attempting to retrieve). – Mark B Mar 19 '12 at 18:45
  • 1
    Minor point: as long as you're using boost, you might as well use the base class "boost::noncopyable" to mark a class as noncopayble. – Edward Loper Mar 19 '12 at 18:46
  • @Oli, @Minty: This did occur to me, and you're probably right. Three reasons why I decided to try my way: 1) Different sections of code can define their own `Key`s that aren't visible to everyone 2) This allows 'unset' values, although boost::optional does that too 3) (minor point) I only pay for memory I use (plus the std::map). @Mark: I will be adding lots of different types from the 'user facing' end of the code, saves me creating lots of containers. @Edward: Thanks! – Martin Mar 19 '12 at 19:22
  • @Martin Containers are cheap and if you wrap access in a specialized template your clients don't even need to know how many you have. They only need to know the type of data being extracted. – Mark B Mar 20 '12 at 15:17

1 Answers1

0

1) Looks nice to me, a clever solution

2) I guess you're afraid that someone will copy the key and its address will change. If that's your concern, keep an "original address" field in your KeySuperType. During construction set the original address to this, during copying set the original address to the original address of the right hand (source). Use this original address to access the map contents. I really couldn't think of a compile time solution to this, since at compile time, compilation units will not know about each other. You could assign unique ID's to the keys earliest in link time, and getting the address of global variables is sort of equivalent to that. The only weak point I can see with this solution is if you define the same key in two dynamic shared libraries without extern, they'll silently have their own versions of the key with different addresses. Of course, if everything goes into the same binary, you won't have that problem, since two declarations without extern will cause a "Multiple Declaration" linker error.

3) If your problem with boost::any is depending on boost (which is more OK then you think), then implement any yourself, it's surprisingly simple. If the problem is performance, then static_cast<> also seems OK to me, as long as you keep the internals of your PropertiesMap away from those that don't know what they're doing...

enobayram
  • 4,650
  • 23
  • 36
  • 2) I thought of doing this, I didn't because I was hoping to find a solution that safely allows non static `Key`s. Ideally something like a template metaprogrammed sequential index, but this doesn't seem [possible](http://stackoverflow.com/questions/1708458/template-metaprogram-converting-type-to-unique-number). 3) I'm happy to depend on boost, just wanted a backup if RTTI was slow/unavailable. From what I can see my solution is `static_cast` safe. You've thought along the same lines as me and helped me consolidate my thoughts so I'm accepting your answer. Thanks! – Martin Mar 20 '12 at 21:38
  • Glad I could help you move on :) Re non static `Key`s, this solution should be safe, as long as people don't **accidentally** create new keys. For that, not providing a no-argument constructor for keys would help I suppose, give a constructor that receives an input that's called `NEW_KEY`. – enobayram Mar 21 '12 at 06:16
  • BTW, If you want your keys to be unique by variable name, you could also define a preprocessor macro (carefully), that defines a variable, and also keeps the name of the variable as a string in the variable. Then use that string as the key. You could still keep your compile time type checking with this approach. – enobayram Mar 21 '12 at 06:21