2011-12-02 124 views
99
#include <iostream> 

struct a 
{ 
    enum LOCAL_A 
    { 
    A1, 
    A2 
    }; 
}; 
enum class b 
{ 
    B1, 
    B2 
}; 

int foo(int input) 
{ 
    return input; 
} 

int main(void) 
{ 
    std::cout<<foo(a::A1)<<std::endl; 
    std::cout<<foo(static_cast<int>(b::B2))<<std::endl; 
} 

a::LOCAL_A是强类型枚举试图实现,但有一个小的区别:正常枚举可以转换为整数类型,而强类型枚举不能没有强制类型转换。如何自动将强类型枚举转换为int?

那么,有没有办法将一个强类型枚举值转换为一个整型没有转换?如果是,如何?

回答

77

强类型枚举旨在为你在你的问题中提到,解决许多问题,并不仅范围界定问题:

  1. 提供类型安全,从而消除了整数提升隐式转换为整。
  2. 指定基础类型。
  3. 提供强有力的范围。

因此,不可能将强类型的枚举隐式转换为整数,甚至是其基础类型 - 这就是主意。所以你必须使用static_cast来明确转换。

如果你的唯一的问题就是范围界定,你真的希望有隐含的推广整数,那么你最好不要使用不强类型与结构的范围枚举它在声明。

希望它能帮助!

12

没有没有自然的方式

事实上,在C++ 11中强烈键入enum class后面的动机之一就是为了防止它们无声转换为int

+0

查看Khurshid Normuradov的回复。它来自'自然的方式',并且'C++编程语言(第4版)'的意图非常符合。它不是以“自动的方式”进行的,这对它来说很好。 – PapaAtHome

+0

@PapaAtHome我不明白这比static_cast的好处。打字或代码清洁没有太多变化。这里的自然方式是什么?函数返回值? –

+0

@ user2876962对我而言,好处在于,它不像Iammilind所说的那样是自动的或“无声的”。这可以防止缺陷找到错误。你仍然可以做演员,但你不得不考虑。这样你就知道你在做什么。 对我来说,这是“安全编码”习惯的一部分。我更喜欢没有自动完成转换,因为它可能会引入错误。如果你问我,那么在这个类别中,与类型系统相关的C++ 11中的一些变化就属于这一类。 – PapaAtHome

104

正如其他人所说,你不能有一个隐式转换,这是副设计。

如果您想要,您可以避免需要在演员表中指定基础类型。

template <typename E> 
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept { 
    return static_cast<typename std::underlying_type<E>::type>(e); 
} 

std::cout << foo(to_underlying(b::B2)) << std::endl; 
+0

将这个声明为'static'会很好。 – ShitalShah

+5

@ShitalShah没有任何意义。 –

+0

不确定你的意思。我现在将它用作静态方法。 – ShitalShah

14
#include <cstdlib> 
#include <cstdio> 
#include <cstdint> 

#include <type_traits> 

namespace utils 
{ 

namespace details 
{ 

template< typename E > 
using enable_enum_t = typename std::enable_if< std::is_enum<E>::value, 
               typename std::underlying_type<E>::type 
              >::type; 

} // namespace details 


template< typename E > 
constexpr inline details::enable_enum_t<E> underlying_value(E e)noexcept 
{ 
    return static_cast< typename std::underlying_type<E>::type >(e); 
} 


template< typename E , typename T> 
constexpr inline typename std::enable_if< std::is_enum<E>::value && 
              std::is_integral<T>::value, E 
             >::type 
to_enum(T value) noexcept 
{ 
    return static_cast<E>(value); 
} 

} // namespace utils 




int main() 
{ 
    enum class E{ a = 1, b = 3, c = 5 }; 

    constexpr auto a = utils::underlying_value(E::a); 
    constexpr E b = utils::to_enum<E>(5); 
    constexpr auto bv = utils::underlying_value(b); 

    printf("a = %d, b = %d", a,bv); 
    return 0; 
} 
+2

这不会减少键入或使代码更清晰,并且会带来副作用,使得在大型项目中发现这种隐式转换更加困难。 Static_cast比这些结构更容易搜索项目范围。 –

10

希望这有助于你或别人

enum class EnumClass : int //set size for enum 
{ 
    Zero, One, Two, Three, Four 
}; 

union Union //This will allow us to convert 
{ 
    EnumClass ec; 
    int i; 
}; 

int main() 
{ 
using namespace std; 

//convert from strongly typed enum to int 

Union un2; 
un2.ec = EnumClass::Three; 

cout << "un2.i = " << un2.i << endl; 

//convert from int to strongly typed enum 
Union un; 
un.i = 0; 

if(un.ec == EnumClass::Zero) cout << "True" << endl; 

return 0; 
} 
+26

这被称为“类型双关”,虽然由某些编译器支持,但不是可移植的,因为C++标准说,在将“un.i”设置为“活动成员”并且只能读取活动成员。 –

+6

@JonathanWakely你在技术上是正确的,但我从来没有见过一个编译器,这不能可靠地工作。像这样的东西,匿名工会和#pragma曾经是事实标准。 – BigSandwich

+2

为什么使用标准明确禁止的事情,当一个简单的演员会做?这是错误的。 –

3

正如许多人说,没有办法不增加开销,太多的复杂自动转换,但可以减少你打字并通过使用lambdas使其看起来更好,如果在场景中某些演员阵容将被使用一点。这会增加一些函数开销调用,但与长static_cast字符串相比,这会使代码更具可读性,如下所示。这可能不是项目范围广泛的,但只有一流的范围。

#include <bitset> 
#include <vector> 

enum class Flags { ......, Total }; 
std::bitset<static_cast<unsigned int>(Total)> MaskVar; 
std::vector<Flags> NewFlags; 

----------- 
auto scui = [](Flags a){return static_cast<unsigned int>(a); }; 

for (auto const& it : NewFlags) 
{ 
    switch (it) 
    { 
    case Flags::Horizontal: 
     MaskVar.set(scui(Flags::Horizontal)); 
     MaskVar.reset(scui(Flags::Vertical)); break; 
    case Flags::Vertical: 
     MaskVar.set(scui(Flags::Vertical)); 
     MaskVar.reset(scui(Flags::Horizontal)); break; 

    case Flags::LongText: 
     MaskVar.set(scui(Flags::LongText)); 
     MaskVar.reset(scui(Flags::ShorTText)); break; 
    case Flags::ShorTText: 
     MaskVar.set(scui(Flags::ShorTText)); 
     MaskVar.reset(scui(Flags::LongText)); break; 

    case Flags::ShowHeading: 
     MaskVar.set(scui(Flags::ShowHeading)); 
     MaskVar.reset(scui(Flags::NoShowHeading)); break; 
    case Flags::NoShowHeading: 
     MaskVar.set(scui(Flags::NoShowHeading)); 
     MaskVar.reset(scui(Flags::ShowHeading)); break; 

    default: 
     break; 
    } 
} 
30

A C++ 14版本由R. Martinho Fernandes提供的将是答案:

template <typename E> 
constexpr auto to_underlying(E e) noexcept 
{ 
    return static_cast<std::underlying_type_t<E>>(e); 
} 

与前面的答案,这将与任何类型的枚举和基础类型的工作。我添加了noexcept关键字,因为它永远不会抛出异常。


更新
这也由斯科特迈尔斯出现在有效的现代C++。参见第10项(它在本书副本的最后一页中有详细说明)。

4

在其他答案中给出了没有隐式转换(按设计)的原因。

我个人使用一元operator+从枚举类的基本类型的转换:

template <typename T> 
constexpr auto operator+(T e) noexcept 
    -> std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>> 
{ 
    return static_cast<std::underlying_type_t<T>>(e); 
} 

其中给出相当小“键入开销”:

std::cout << foo(+b::B2) << std::endl; 

当我实际使用宏来一次性创建枚举和操作符函数。

#define UNSIGNED_ENUM_CLASS(name, ...) enum class name : unsigned { __VA_ARGS__ };\ 
inline constexpr unsigned operator+ (name const val) { return static_cast<unsigned>(val); } 
4

这似乎是不可能与本地enum class,但也许你可以模拟一个enum classclass

在这种情况下,

enum class b 
{ 
    B1, 
    B2 
}; 

将相当于:

class b { 
private: 
    int underlying; 
public: 
    static constexpr int B1 = 0; 
    static constexpr int B2 = 1; 
    b(int v) : underlying(v) {} 
    operator int() { 
     return underlying; 
    } 
}; 

这大部分是等价的t到原来的enum class。您可以直接返回b::B1进行返回类型为b的功能。你可以用它来做switch case等。

在这个例子的精神,你可以使用模板(可能与其他东西一起)概括和嘲笑由enum class语法定义的任何可能的对象。

3

简短的回答是你不能像上面的帖子指出的那样。但对于我来说,我只是不想杂乱的命名空间,但仍然有隐式转换,所以我只是做:

#include <iostream> 

using namespace std; 

namespace Foo { 
    enum { bar, baz }; 
} 

int main() { 
    cout << Foo::bar << endl; // 0 
    cout << Foo::baz << endl; // 1 
    return 0; 
} 

的命名空间排序的增加类型安全的层,而我没有静态将任何枚举值转换为基础类型。