定义基于模板的映射指针,以便在验证范围时将 int 解析为枚举



我想提供一个简单的机制,它将 int 转换为枚举类型,并在此过程中提供一些错误检查。这将确保 int 不会超出其声明的枚举范围。 我想出了以下函数,它利用了 std::map:

#include <map>
#include <type_traits>
enum A
{
    a = 0,
    b,
    c
};
enum B
{
    d = 10,
    e,
    f
};
std::map<int, A> mA;
std::map<int, B> mB;
template<typename T> T ParseEnum(int nVal)
{
    std::map<int, T>* p;
    if (std::is_same<T, A>::value)
        p = &mA; //compiler error
    else if (std::is_same<T, B>::value)
        p = &mB; //compiler error
    return static_cast<T>(p->at(nVal));
}

int _tmain(int argc, _TCHAR* argv[])
{
    mA.insert(std::pair<int, A>(0, A::a));
    mA.insert(std::pair<int, A>(1, A::b));
    mA.insert(std::pair<int, A>(2, A::c));
    mB.insert(std::pair<int, B>(10, B::d));
    mB.insert(std::pair<int, B>(11, B::e));
    mB.insert(std::pair<int, B>(12, B::f));
    try
    {
        A eA = ParseEnum<A>(1); //ok, should resolve to A::b;
        B eB = ParseEnum<B>(16); //should throw an exception;
    }
    catch (std::out_of_range&)
    {
    }
    return 0;
}

不幸的是,我在将地图引用分配给基于模板的地图指针时遇到问题,如以下编译器错误所示:

error C2440: '=' : cannot convert from 'std::map<int,A,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>> *' to 'std::map<int,B,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>> *'

error C2440: '=' : cannot convert from 'std::map<int,B,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>> *' to 'std::map<int,A,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>> *'

有没有办法定义这样一个基于模板的指针,还是我在这里不走运?

模板专业化更适合:

//declare the ParseEnum function template
template<typename T> 
T ParseEnum(int nVal);
//specialization for when ParseEnum is instantiated with A
template<>
A ParseEnum<A> (int nVal)
{
    return mA.at(nVal);
}
//specialization for B
template<>
B ParseEnum<B> (int nVal)
{
    return mB.at(nVal);
}

或者这可能更灵活。它创建一个struct,该将保存对其模板参数对应的map的引用:

//declare a struct which will hold a reference to the map we want
template <typename T>
struct GetMap
{
    static std::map<int,T> &map;
};
//when instantiated with A, the map member is a reference to mA
template<>
std::map<int,A> &GetMap<A>::map = mA;
//similarly for B and mB
template<>
std::map<int,B> &GetMap<B>::map = mB;
template<typename T> T ParseEnum(int nVal)
{
    //GetMap<T>::map will be a reference to the correct map
    return GetMap<T>::map.at(nVal);
}

我想提供一个简单的机制,将 int 转换为枚举 类型,并在此过程中提供一些错误检查。这将 确保 int 不会超出其声明的枚举范围。

这听起来像是枚举的糟糕使用。理想情况下,从语义的角度来看,枚举应该只是具有一组固定值的独特类型,并且它作为int实现的事实应该是一个实现细节(很像私有成员变量)。

您可能想要查看 C++11 枚举类。

return static_cast<T>(p->at(nVal));

at从何而来?你需要这样的东西才能被抛出std::out_of_range

std::map<int, T>::iterator find_iter = p->find(nVal);
if (find_iter == p->end())
{
    throw std::out_of_range("illegal value");
}
return static_cast<T>(find_iter->second);

(C++11 为std::map提供at

int _tmain(int argc, _TCHAR* argv[])

int main(int argc, char* argv[])标准C++。或者只是int main(),因为无论如何你都不使用这些参数。

mA.insert(std::pair<int, A>(0, A::a));

你应该使用 std::make_pair 让模板类型推导自动计算出参数类型,即:

mA.insert(std::make_pair(0, A::a));

现在让我们看看是什么导致了编译器错误:

template<typename T> T ParseEnum(int nVal)
{
    std::map<int, T> * p;
    if (std::is_same<T, A>::value)
        p = &mA; //compiler error
    else if (std::is_same<T, B>::value)
        p = &mB; //compiler error
    return static_cast<T>(p->at(nVal));
}

在此函数中,T要么A,要么B。在第一种情况下,它变成:

// pseudo code:
A ParseEnumA(int nVal)
{
    std::map<int, A> * p;
    if (std::is_same<A, A>::value)
        p = &mA; // NOT a compiler error
    else if (std::is_same<A, B>::value)
        p = &mB; //compiler error
    return static_cast<A>(p->at(nVal));
}

在第二种情况下:

// pseudo code:
B ParseEnumB(int nVal)
{
    std::map<int, B> * p;
    if (std::is_same<B, A>::value)
        p = &mA; // compiler error
    else if (std::is_same<B, B>::value)
        p = &mB; // NOT a compiler error
    return static_cast<B>(p->at(nVal));
}

看看会发生什么?无论哪种情况,其中一个赋值都有效,而另一个赋值失败,因为std::map<int, A>std::map<int, B>是不相关的类型。

有没有办法定义这样一个基于模板的指针,或者我出局了 运气在这里?

对于非常快速和肮脏的修复,您可以侥幸逃脱 reinterpret_cast .它不会在运行时执行,但会消除编译时错误。

if (std::is_same<T, A>::value)
    p = reinterpret_cast<std::map<int, T>*>(&mA);
else if (std::is_same<T, B>::value)
    p = reinterpret_cast<std::map<int, T>*>(&mB);

这是完整的程序:

#include <map>
#include <stdexcept>
#include <iostream>
enum A
{
    a = 0,
    b,
    c
};
enum B
{
    d = 10,
    e,
    f
};
std::map<int, A> mA;
std::map<int, B> mB;
template<typename T> T ParseEnum(int nVal)
{
    std::map<int, T>* p;
    if (std::is_same<T, A>::value)
        p = reinterpret_cast<std::map<int, T>*>(&mA);
    else if (std::is_same<T, B>::value)
        p = reinterpret_cast<std::map<int, T>*>(&mB);
    std::map<int, T>::iterator find_iter = p->find(nVal);
    if (find_iter == p->end())
    {
        throw std::out_of_range("illegal value");
    }
    return static_cast<T>(find_iter->second);
}

int main()
{
    mA.insert(std::make_pair(0, A::a));
    mA.insert(std::make_pair(1, A::b));
    mA.insert(std::make_pair(2, A::c));
    mB.insert(std::make_pair(10, B::d));
    mB.insert(std::make_pair(11, B::e));
    mB.insert(std::make_pair(12, B::f));
    try
    {
        A eA = ParseEnum<A>(1); //ok, should resolve to A::b;
        std::cout << "OKn";
        B eB = ParseEnum<B>(16); //should throw an exception;
    }
    catch (std::out_of_range const& exc)
    {
        std::cerr << exc.what() << "n";
    }
}

但问题是:这是一个很好的解决方案吗?

我不这么认为。您应该使用 C++11 枚举类并消除将int转换为enum的必要性!将int视为实现细节,例如私有成员变量。

解决此问题的一种方法:

template <typename T> struct MapPtr;
template <> struct MapPtr<A> 
{
   static constexpr std::map<int, A>* const value =  &mA;
};
template <> struct MapPtr<B> 
{
   static constexpr std::map<int, B>* const value =  &mB;
};
template<typename T> T ParseEnum(int nVal)
{
    std::map<int, T>* ptr = MapPtr<T>::value;
    return static_cast<T>(ptr->at(nVal));
}

这是处理对应于AB map的不同策略。使用类模板提供对map的访问,而不是将它们作为全局数据。

#include <iostream>
#include <type_traits>
#include <map>
#include <stdexcept>
enum A
{
    a = 0,
    b,
    c
};
enum B
{
    d = 10,
    e,
    f
};
template <typename T> struct MapContainer 
{
   static std::map<int, T>& getMap()
   {
      static std::map<int, T> theMap;
      return theMap;
   }
};
template<typename T> T ParseEnum(int nVal)
{
    std::map<int, T>& theMap = MapContainer<T>::getMap();
    return static_cast<T>(theMap.at(nVal));
}
int main(int argc, char* argv[])
{
    std::map<int, A>& mA = MapContainer<A>::getMap();
    mA.insert(std::pair<int, A>(0, A::a));
    mA.insert(std::pair<int, A>(1, A::b));
    mA.insert(std::pair<int, A>(2, A::c));
    std::map<int, B>& mB = MapContainer<B>::getMap();
    mB.insert(std::pair<int, B>(10, B::d));
    mB.insert(std::pair<int, B>(11, B::e));
    mB.insert(std::pair<int, B>(12, B::f));
    try
    {
        A eA = ParseEnum<A>(1); //ok, should resolve to A::b;
        std::cout << "ParseEnum<A>(1): " << eA << std::endl;
        B eB = ParseEnum<B>(16); //should throw an exception;
        std::cout << "ParseEnum<B>(16): " << eB << std::endl;
    }
    catch (std::out_of_range&)
    {
    }
    return 0;
}

最新更新