Search code examples
c++templatesc++11dictionaryboost-mpl

Simple MPL-like type map template


I am trying to find an elegant way to implement something like a very simple boost::mpl type map.
Using MPL or any other boost library is not an option in my context. Besides, the only operations I really need are initialization and lookup. Storing compile-time values would also be nice (which I could do using boost::fusion::set, if I used a boost-based solution).
I know how to do what I want with a traits class, but I think that the syntax is a bit too verbose and has some other small drawbacks, e.g. you can't define traits specializations inside a class declaration, and, well, generally it's not all neatly in one place.

Here's what I want to achieve using traits:

// base class definitions
class Foo {};
class Bar {};
class Baz {};

// traits base
template<typename T>
struct MyTraits {};

// traits specializations
template<>
struct MyTraits<Foo> {
    static constexpr const char* name = "foo";
    typedef double type;
};
template<>
struct MyTraits<Bar> {
    static constexpr const char* name = "bar";
    typedef std::string type;
};

// generic worker function
template<typename T>
void doStuff(const T& arg) {
    std::cout << "got a " << MyTraits<T>::name << " - size: " << sizeof(typename MyTraits<T>::type) << std::endl;
}

int main(int, char*[]) {
    Foo foo;
    Bar bar;
    Baz baz;

    doStuff(foo);
    doStuff(bar);
    // this fails, as expected:
    //doStuff(baz);
}

And here's the kind of syntax I would like to be able to use:

typedef TMapTemplate<
    TMapEntry<Foo, double, "foo">,
    TMapEntry<Bar, string, "bar">
> tTraitsMap;

// generic worker function
template<typename T>
void doStuff(const T& arg) {
    std::cout << "got a " << at<tTraitsMap,T>::value<1>() << " - size: " << sizeof(at<tTraitsMap,T>::type<0>) << std::endl;
}

(Something like this would be ideal, but I would be happy with a simple one-to-one type map, really)


Solution

  • So, I ended up tinkering something that does more or less what I want. It's not perfect by any means, but does match a simple version of what I wanted:

    Templates:

    struct NotFound {};
    
    template <typename TK, typename TV>
    struct TMapEntry {
    };
    
    template <typename... TEntries>
    struct TMap {
    };
    
    template <typename TK, typename TV, typename... TEntries>
    struct TMap<TMapEntry<TK, TV>, TEntries...> {
        typedef TMap<TEntries...> tNext;
        template<typename T>
        struct HeadResolver {
            static constexpr bool match = is_same<TK, T>::value;
        };
    
        template<typename T>
        using at = typename conditional<HeadResolver<T>::match, TV, typename tNext::template at<T>>::type;
    };
    
    template<>
    struct TMap<> {
        template<typename T>
        using at = NotFound;
    };
    

    Usage:

    class Foo {};
    class Bar {};
    class Baz {};
    
    void test() {
        typedef TMap<> tEmptyMap;
        static_assert(is_same<tEmptyMap::at<Foo>, NotFound>::value, "found something in empty map");
        static_assert(is_same<tEmptyMap::at<int>, NotFound>::value, "found something in empty map");
    
        typedef TMap<
            TMapEntry<Foo, int>,
            TMapEntry<Bar, float>,
            TMapEntry<void, string>
        > tMap;
        static_assert(is_same<tMap::at<Foo>, int>::value, "map mismatch");
        static_assert(is_same<tMap::at<Bar>, float>::value, "map mismatch");
        static_assert(is_same<tMap::at<void>, string>::value, "map mismatch");
        static_assert(is_same<tMap::at<Baz>, NotFound>::value, "map mismatch");
    } 
    

    A couple points I thought of while doing this:

    • It does not check or guarantee key unicity.
    • I could remove the TMapEntry template altogether and just have each "Map node" handle the 2 head types (both key and value). Usage would then look something like this: typedef TMap<Foo, int, Bar, float, void, string> tMap;, which looks more flexible, but may hide errors.
    • Variadic templates and template aliases make this pretty smooth, with only 20 or so lines of code.