我想提供一个简单的机制,它将 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> ↦
};
//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));
}
这是处理对应于A
和B
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;
}