For a bit of fun, I created a very basic compile-time type-value map class, as follows:
template <typename T, auto V>
struct TypeValuePair { };
template <typename... TypeValuePairs>
struct TypeValueMap
{
struct MapItems : TypeValuePairs... { };
template <typename T, auto V>
static constexpr auto Lookup(TypeValuePair<T, V>*)
{ return V; }
template <auto V, typename T>
static T Lookup(TypeValuePair<T, V>*);
template <typename T>
static constexpr auto ValueFor = Lookup<T>((MapItems*)nullptr);
template <auto V>
using TypeFor = decltype(Lookup<V>((MapItems*)nullptr));
};
to be used in a way such as this:
struct A; struct B; struct C;
enum class Values { A, B, C };
using Map = TypeValueMap<
TypeValuePair<A, Values::A>,
TypeValuePair<B, Values::B>,
TypeValuePair<C, Values::C>,
TypeValuePair<struct Other, 0>
>;
static_assert(Map::ValueFor<A> == Values::A, "");
static_assert(Map::ValueFor<B> == Values::B, "");
static_assert(Map::ValueFor<C> == Values::C, "");
static_assert(Map::ValueFor<struct Other> == 0, "");
static_assert(std::is_same<Map::TypeFor<Values::A>, A>::value, ""); //***
static_assert(std::is_same<Map::TypeFor<Values::B>, B>::value, "");
static_assert(std::is_same<Map::TypeFor<Values::C>, C>::value, "");
static_assert(std::is_same<Map::TypeFor<0>, struct Other>::value, ""); //***
Unfortunately, the two lines marked //***
fail with the error failed template argument deduction or similar on clang and g++ (the two compilers I have to hand). I can understand why this might be because Values::A
has the value 0
so the two potentially collide. However, I would argue that they are in fact different types – one is plain integer, the other an enum class
with underlying type integer – and so shouldn't in fact collide.
If I implement my map class differently, like so:
template <typename T, auto V>
struct TypeValuePair
{
protected:
static constexpr auto Lookup(T*)
{ return V; }
template <template <auto> class Wrapper>
static T Lookup(Wrapper<V>*);
};
template <typename... TypeValuePairs>
struct TypeValueMap
{
struct MapItems : TypeValuePairs...
{ using TypeValuePairs::Lookup...; };
template <auto> struct LookupByValue;
template <typename T>
static constexpr auto ValueFor = MapItems::Lookup((T*)nullptr);
template <auto V>
using TypeFor = decltype(MapItems::Lookup((LookupByValue<V>*)nullptr));
};
then there are no template argument deduction errors.
Therefore the question is, is the failure to deduce the template argument in the first implementation due to a bug in the compilers (given my assertion that integer and enum class
should be treated as different types and not collide) or is it a misunderstanding on my side of what is possible with template argument deduction (I am not a language lawyer!), or some other bug in my implementation?
struct MapItems : TypeValuePairs... { using TypeValuePairs::Lookup...; };
. – Evg