1

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)

Martin J.
  • 5,028
  • 4
  • 24
  • 41
  • how are you going to use `fusion::set` if you cannot use Boost? – TemplateRex Feb 27 '14 at 08:37
  • @TemplateRex: I am not going to use fusion... I was trying to express that if I could use `boost::mpl` to solve the type-to-map issue, I could also use `boost::fusion` to solve the type-to-value issue (since `mpl` does not provide that). Edited to clarify accordingly – Martin J. Feb 27 '14 at 08:44
  • Just curious though, why is it that you cannot use Boost for this? Suppose you would use the [bcp tool](http://www.boost.org/doc/libs/1_55_0/tools/bcp/doc/html/index.html) to extract all dependencies from `boost::mpl::map`, would that constitute a library solution or your own private code? – TemplateRex Feb 27 '14 at 08:52
  • @TemplateRex: The project this issue popped on has a strict library control policy, and `boost` is off-limit. I suppose that if I needed to, I could extract the relevant code as you suggested, and it would be admissible. In practice, trait classes work well enough for our needs, so the practical aspects of my question end here. However, I keep thinking that this kind of map wouldn't be too hard to express with the new C++11 language constructs such as variadic templates, tuples and so on, so this is more like a style exercise question. – Martin J. Feb 27 '14 at 08:58

1 Answers1

0

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.
Martin J.
  • 5,028
  • 4
  • 24
  • 41