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)
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:
typedef TMap<Foo, int, Bar, float, void, string> tMap;
, which looks more flexible, but may hide errors.