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

Define template based map pointer in order to parse int to enum while validating range

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#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(16); //should throw an exception;
    }
    catch (std::out_of_range&)
    {

    }

    return 0;
}

不幸的是,我无法将映射引用分配给基于模板的映射指针,如以下编译器错误所示:

error C2440: '=' : cannot convert from 'std::map,std::allocator>> *' to 'std::map,std::allocator>> *'

error C2440: '=' : cannot convert from 'std::map,std::allocator>> *' to 'std::map,std::allocator>> *'

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


I would like provide a simple mechanism which casts an int to an enum
type and also provides some error checking along the way. This would
make sure, that the int doesn't fall out its declared enum range.

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

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

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

at来自哪里?你需要这样的东西才能得到std::out_of_range

1
2
3
4
5
6
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)

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

应该是标准C ++中的int main(int argc, char* argv[])。或者只是int main(),因为你还是不使用参数。

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

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

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

现在让我们看看导致编译器错误的原因:

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T> T ParseEnum(int nVal)
{
    std::map<int, T> * p;

    if (std::is_same<T, A>::value)
        p = &amp;mA; //compiler error

    else if (std::is_same<T, B>::value)
        p = &amp;mB; //compiler error

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

在此函数中,TAB。在第一种情况下,它变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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));
}

在第二种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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(p->at(nVal));
}

走着瞧吧?在任何一种情况下,其中一个分配都有效,另一个失败,因为std::mapstd::map是不相关的类型。

Is there a way to define such a template based pointer or am I out of
luck here?

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

1
2
3
4
5
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);

这是完整的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#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 <<"OK
"
;
        B eB = ParseEnum(16); //should throw an exception;
    }
    catch (std::out_of_range const& exc)
    {
        std::cerr << exc.what() <<"
"
;
    }
}

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

我不这么认为。您应该使用C ++ 11枚举类,并删除将int转换为enum的必要性!考虑int一个实现细节,就像私有成员变量一样。


模板专业化更适合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//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 (int nVal)
{
    return mB.at(nVal);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//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::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);
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#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::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(16); //should throw an exception;
        std::cout <<"ParseEnum(16):" << eB << std::endl;
    }
    catch (std::out_of_range&)
    {

    }

    return 0;
}

解决问题的一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T> struct MapPtr;

template <> struct MapPtr<A>
{
   static constexpr std::map<int, A>* const value =  &mA;
};

template <> struct MapPtr
{
   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));
}