0

Take the following "type" dictionary -- its keys are supposed to be types, and values an instance of that type:

class TypeDictionary {
public: 
   template<class T> void insert(T t);
   template<class T> T& get();
   // implementation be here -- and the question is about this
}; 

struct Foo;
struct Bar;

void userOfTypeDictionary() {

    TypeDictionary td;

    td.insert( Foo() ); 
    td.insert( Bar() );
    td.insert( double(3.14) ); 
    // and other unknown (to TypeDictionary) 
    // list of types attached


    // later on, in a different scope perhaps
    Foo& f = td.get<Foo>() ;
    Bar& f = td.get<Bar>(); 
    double pi = tg.get<double>();
    // ...
}

This particular TypeDictionary has a growing set of types "registered", but of course, the facility should allow an arbitrary set of types, and potentially a different set for each instance of this class.

As for a realistic motivating use case, think of a plugin manager. At any given time of its life an arbitrary heterogeneous set of objects are attached to it, and the it is the manager's job to manage the life time of the attached objects and return (a reference) to them when queried in a type-safe manner.

Any ideas on whether such a thing is possible? It is fine if a strategy involves using a library, like Boost.Fusion or similar.

Nick
  • 931
  • 6
  • 17

1 Answers1

2

A minimal but probably sub-optimal way of doing this is using the type-wise versions of std::get that are only available in C++14. This way your dictionary would simply derive or wrap a tuple:

template<typename... T>
struct dict
{
    std::tuple<T...> t;

    template<typename E>
    void insert(E e) { std::get<E>(t) = e; }

    template<typename E>
    E& get() { return std::get<E>(t); }
};

struct Foo {};
struct Bar {};

using TypeDictionary = dict<Foo, Bar, double>;

The above gives the desired behavior with clang -std=c++1y but fails with g++ up to 4.8.2 at least.

Anyhow, the idea is that you have a predefined list of type-keys given up-front, and your dictionary behaves like a fixed-size flat set, meaning that it includes size for its elements (at least non-empty ones) even if you don't call insert(), in which case these elements are default-constructed.

I have paid no particular attention here to giving const/non-const versions of methods, or move operations. The above is only to get the idea. Of course, this is so simple that you could just use std::tuple and std::get() directly.

To have the dictionary with a fixed type regardless of its element types is quite harder; it's a matter of type erasure and in this respect it resembles std::function. To get a really dynamic type-safe heterogeneous container (with no casts, virtual functions etc.) is not possible with the given syntax I think. What is possible is to get a new dictionary of a new (enlarged) type after each insert() operation, e.g.

auto new_d = d.insert(3.14);

This would simply concatenate the existing tuple<T...> with the new element to give a new tuple<T...,double>.

iavr
  • 7,547
  • 1
  • 18
  • 53
  • thanks for your answer! So, as you image the first tuple version isn't addressing the most important spec, which is that the list of types aren't predefined (if it were, Boost.Fusion can be used just fine portably, in particular, boost::fusion::set). The second half of you comment is more interesting, and in fact, there is a different underlying type for sure with each insertion. But it'd be more interesting if TypeDisctionary somehow manages it, via some type erasure or else.. – Nick Feb 16 '14 at 21:23
  • You could use something like [boost::any](http://www.boost.org/doc/libs/1_55_0/doc/html/any.html) for the type erasure part, although it contains nasty low-level code and uses the free store if I remember correctly (I've made a similar library that at least doesn't use the free store). This enables heterogeneous containers but by itself it doesn't solve the problem of searching for a given key among different types. – iavr Feb 16 '14 at 21:45