c++ 打印连续枚举

mpbci0fu  于 2023-03-05  发布在  其他
关注(0)|答案(3)|浏览(203)

我希望只打印出“分组”枚举,但无法获得预期的行为。因此,基本上打印出指定基Values中的所有枚举,直到没有任何后续的连续枚举值。枚举的每个“分组”都可以通过与掩码0xFFFF0000进行AND运算来确定
诀窍是我可以迭代_map枚举,但这样就没有一种简单的方法来检查对应的键是否存在。find方法需要一个键,所以这不会有帮助。
P.S.:_map已经存在,用于“其他”目的,所以我不能更改它

enum class Values : uint32_t
{
  one    =  0x00000000,
  oneOne =  0x00000001,
  oneTwo =  0x00000002,

  two =     0x00010000,
  twoOne =  0x00010001,
  twoTwo =  0x00010002,

  three    =  0x00020000,
  threeOne =  0x00020001,
  threeTwo =  0x00020002,
  //...

  MAX
};

std::unordered_map<std::string, Values> _map =
{
    {"one",     Values::one},
    {"oneOne",  Values::oneOne},
    {"oneTwo",  Values::oneTwo},
    {"two",     Values::two},
    {"twoOne",  Values::twoOne},
    {"twoTwo",  Values::twoTwo}
};

我得到了如下的结果,但是没有一种方法可以在枚举值不存在的地方“中断”。

void foo(Values base)
{
    uint32_t mask = static_cast<uint32_t>(base) & 0xffff0000;

    for (Values i = base; i < Values::MAX; i = static_cast<Values>(static_cast<uint32_t>(i) + 1)) 
    {
        uint32_t curMask = static_cast<uint32_t>(i) & 0xffff0000;

        if (curMask != mask) 
        {
            break;  // stop if we've reached a different upper 16 bits value
        }
        
        std::cout << std::hex << static_cast<uint32_t>(i) << "\n";
    }
}

// expected calls with expected output
foo(Values::one);      // should print: one, oneOne, oneTwo
foo(Values::oneOne);   // should print: oneOne, oneTwo
foo(Values::twoTwo);   // should print: twoTwo
dsf9zpds

dsf9zpds1#

如果你有

const std::unordered_map<std::string, Values> _map = {
    {"one", Values::one},       
    {"oneOne", Values::oneOne},
    {"oneTwo", Values::oneTwo}, 
    {"two", Values::two},
    {"twoOne", Values::twoOne}, 
    {"twoTwo", Values::twoTwo}};

我建议添加反向查找std::map

const auto _rmap = []{
    std::map<Values, decltype(_map.cbegin())> rv;
    for(auto it = _map.cbegin(); it != _map.cend(); ++it)
        rv.emplace(it->second, it);
    return rv;
}();

......能够使用std::map::upper_bound获得一个迭代器,该迭代器指向您感兴趣的子组之后的第一个元素:

void foo(Values base) {
    if (auto it = _rmap.find(base); it != _rmap.end()) {
        // mask contains the highest possible value in the subgroup:
        auto mask = static_cast<Values>(static_cast<uint32_t>(base) | 0xffff);

        // end is an iterator to the first element after `mask`
        auto end = _rmap.upper_bound(mask);

        for (; it != end; ++it) {
            // dereference the iterator into the original map:
            const auto& [str, val] = *it->second;

            std::cout << str << '\t' << static_cast<uint32_t>(val) << '\n';
        }
    }
}

Demo
如果你更喜欢在反向查找std::map中存储std::string而不是迭代器,这也能很好地工作:

#include <algorithm> // std::transform

const auto _rmap = [] {
    std::map<Values, std::string> rv;
    std::transform(_map.begin(), _map.end(), std::inserter(rv, rv.end()),
                   [](auto&& p) { return std::pair{p.second, p.first}; });
    return rv;
}();

void foo(Values base) {
    if (auto it = _rmap.find(base); it != _rmap.end()) {
        auto mask = static_cast<Values>(static_cast<uint32_t>(base) | 0xffff);
        auto end = _rmap.upper_bound(mask);
        for (; it != end; ++it) {
            const auto& [val, str] = *it;

            std::cout << str << '\t' << static_cast<uint32_t>(val) << '\n';
        }
    }
}

Demo

i34xakig

i34xakig2#

只需将_map反转为另一个Map,并使用它来帮助识别哪些"值"是有效的。
我稍微修改了代码,在foo函数中声明了reverseMap,但是您可以将reverseMap的初始化移动到其他地方,使其成为全局变量或成员变量。

struct ReverseMapContainer
{
    std::unordered_map<uint32_t, std::string> reverseMap;
    ReverseMapContainer()
    {
        for (const auto& v : _map)
        {
            reverseMap.insert({ (uint32_t)(v.second), v.first });
        }
    }
};

void foo(Values base)
{
    static ReverseMapContainer rmc;
    const auto& reverseMap = rmc.reverseMap;

    uint32_t mask = static_cast<uint32_t>(base) & 0xffff0000;
    uint32_t stop = mask + 0x00010000;

    for (uint32_t i = (uint32_t)base; i < stop; i++)
    {
        auto itor = reverseMap.find(i);
        if (itor != reverseMap.end())
        {
            std::cout << itor->second << " ";
        }
        else
        {
            break;
        }
    }
    std::cout << "\n";
}

然后用同样的标准来测试它:

int main()
{
    // expected calls with expected output
    foo(Values::one);      // should print: one, oneOne, oneTwo
    foo(Values::oneOne);   // should print: oneOne, oneTwo
    foo(Values::twoTwo);   // should print: twoTwo
}

结果:

qnakjoqk

qnakjoqk3#

_map已存在,用于“其他”用途,因此无法更改
我会选择不完全尊重这项限制,我的理由是,虽然我接受这个前提(_map已经存在,用于“其他”目的”),我不认为它必然暗示结论(“I can 't change that”).可能是当前的用法阻碍了更改,但也可能不是.如果编写其他代码时假定类型为std::unordered_map<std::string, Values>,那么改变是不可能的。然而,如果代码仅仅假设类型“看起来像什么”,那么改变是可能的。
例如,如果有代码使用类似于

std::unordered_map<std::string, Values>::iterator it = _map.begin();

那么改变类型是有问题的。但是,一个等价的定义是

auto it = _map.begin();

并且这种形式不锁定类型。在形式之间的选择可能是代码编写者在编写代码当天的感觉。因此,要求 * 可能 * 比“不能更改”弱。取决于代码是如何编写的,保持“签名兼容”可能就足够了,甚至可能_map是一个容器,具有适当的operator[]来查找字符串就足够了。
Boost.Bimap是一个值得考虑的选项,这个容器基本上是一个Map,允许按键或值进行查找,此外,它还允许以不同的方式组织键和值--您可以指定键提供无序(散列)查找,并且值提供有序查找。存在一些功能差异,但是如果你的容器在初始化后不需要修改(看起来是这样),那么许多区别就不相关了。
下面的声明旨在给予如何使用Bimap的概念(未测试;基于Boost的文档和示例)。

// Create an alias for the rather long type name.
typedef MapType = boost::bimaps::bimap<boost::bimaps::unordered_set_of<std::string>, Values>;
// Define the bimap.
MapType _fullMap;
// Define the view to be used by existing code.
auto& _map = _fullMap.left;
// Define the reversed view
auto& _mapReversed = _fullMap.right;

这工作得有多好取决于现有代码到底做了什么。左视图与std::unordered_map<std::string, Values>签名兼容,所以它可能“只是工作”。
一个缺点可能是初始化。看起来你可能需要在函数内初始化,而不是在定义点指定值。

void initFullMap() {
    _fullMap.insert(MapType::value_type("one",    Values::one));
    _fullMap.insert(MapType::value_type("oneOne", Values::oneOne));
    _fullMap.insert(MapType::value_type("oneTwo", Values::oneTwo));
    _fullMap.insert(MapType::value_type("two",    Values::two));
    _fullMap.insert(MapType::value_type("twoOne", Values::twoOne));
    _fullMap.insert(MapType::value_type("twoTwo", Values::twoTwo));
}

它的一个优点是数据只存储一次,并且没有需要维护的二级数据结构,所以我认为它值得研究。

相关问题