AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79591480
Accepted
omon
omon
Asked: 2025-04-25 05:25:24 +0800 CST2025-04-25 05:25:24 +0800 CST 2025-04-25 05:25:24 +0800 CST

Como trabalhar de forma limpa com a classe enum e uint8_t sem static_cast excessivo?

  • 772

Atualmente, estou trabalhando em um gerador de jogadas de xadrez voltado para desempenho e usando muitas declarações de classes enum fortemente tipadas para funções como Quadrado, Arquivo, Classificação, Cor, Tipo de Peça, Roque, Direção, etc. Enfrentei o problema de precisar converter constantemente valores de classes enum para o tipo subjacente (uint8_t) e vice-versa para usá-los como índices de array, realizar operações aritméticas, passá-los como argumentos ou combiná-los em lógica. Isso resulta em toneladas de static_cast<uint8_t>(...) repetitivos e feios por todo o código, como no seguinte MRE:

#include <array>
#include <cstdint>
#include <cstdlib>
#include <iostream>

using u8 = uint8_t;
using Bitboard = uint64_t;

enum class File : u8 {
    FA, FB, FC, FD, FE, FF, FG, FH, NB
};

enum class Rank : u8 {
    R1, R2, R3, R4, R5, R6, R7, R8, NB
};

enum class Square : u8 {
    A1, B1, C1, D1, E1, F1, G1, H1,
    A2, B2, C2, D2, E2, F2, G2, H2,
    A3, B3, C3, D3, E3, F3, G3, H3,
    A4, B4, C4, D4, E4, F4, G4, H4,
    A5, B5, C5, D5, E5, F5, G5, H5,
    A6, B6, C6, D6, E6, F6, G6, H6,
    A7, B7, C7, D7, E7, F7, G7, H7,
    A8, B8, C8, D8, E8, F8, G8, H8,
    NB, FIRST = A1, LAST = H8
};

enum class Color : u8 { WHITE, BLACK, NB };
enum class PieceType : u8 { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, NB };

constexpr File getFile(Square sq) {
    return static_cast<File>(static_cast<u8>(sq) % 8);
}

constexpr Rank getRank(Square sq) {
    return static_cast<Rank>(static_cast<u8>(sq) / 8);
}

std::array<std::array<u8, static_cast<u8>(Square::NB)>, static_cast<u8>(Square::NB)> squareDistance{};
std::array<std::array<Bitboard, static_cast<u8>(Square::NB)>, static_cast<u8>(Color::NB)> pawnAttacks{};
std::array<std::array<Bitboard, static_cast<u8>(Square::NB)>, static_cast<u8>(PieceType::NB)> pseudoAttacks{};

void precomputeSquareDistance() {
    for (u8 s1 = static_cast<u8>(Square::FIRST); s1 <= static_cast<u8>(Square::LAST); ++s1) {
        for (u8 s2 = static_cast<u8>(Square::FIRST); s2 <= static_cast<u8>(Square::LAST); ++s2) {
            squareDistance[s1][s2] =
                std::abs(static_cast<int>(getFile(static_cast<Square>(s1))) - static_cast<int>(getFile(static_cast<Square>(s2)))) +
                std::abs(static_cast<int>(getRank(static_cast<Square>(s1))) - static_cast<int>(getRank(static_cast<Square>(s2))));
        }
    }
}

int main() {
    precomputeSquareDistance();
    std::cout << static_cast<int>(squareDistance[static_cast<u8>(Square::A1)][static_cast<u8>(Square::H8)]) << "\n";
    return 0;
}

Escolhi a classe enum de propósito porque tenho muitas enumerações em todo o meu código-fonte. Também prefiro unsigned char em vez de int, pois melhora o desempenho.

No entanto, minhas decisões vêm ao custo de verbosidade e código excessivamente confuso.

Tentei resolver o problema com uma macro para habilitar operadores "compatíveis com u8" (por exemplo, sobrecargas para +, -, ++, comparações, etc.) para tipos de classes enum gerais. Dessa forma, eu poderia evitar escrever todas essas conversões. Mas isso rapidamente se transformou em um milhão de combinações possíveis de tipos enum e uint8_t, além da necessidade de modelos ou metaprogramação de macros, e simplesmente parecia errado e insustentável. Além disso, não resolveu o problema de acessar o array diretamente.

Existe uma maneira limpa, segura e não insana de trabalhar com valores de classe enum como inteiros sem usar static_cast?

O que desenvolvedores experientes em C++ fazem nesses casos? Estou esquecendo de algum padrão de design simples?

Desde já, obrigado.

c++
  • 3 3 respostas
  • 120 Views

3 respostas

  • Voted
  1. Best Answer
    JaMiT
    2025-04-25T08:54:06+08:002025-04-25T08:54:06+08:00

    Mas isso rapidamente se transformou em um milhão de combinações possíveis de tipos de enumeração e uint8_t

    Seu código não demonstra essa necessidade. Ela pode existir no seu código real, mas, ao mesmo tempo, você pode estar tentando criar problemas demais e lidar com muitos problemas de uma só vez. Em vez de tratar isso como um problema monolítico, divida para conquistar. Vejo quatro problemas distintos que podem ser resolvidos individualmente.

    1. É muito provável que seus NBvalores sejam usados ​​como números em vez de enumeradores. Portanto, defina constantes para eles com o tipo inteiro desejado. Isso permite que você pule a conversão onde esses números são usados.
    constexpr u8 SquareCount = static_cast<u8>(Square::NB);
    constexpr u8 ColorCount = static_cast<u8>(Color::NB);
    constexpr u8 PieceCount = static_cast<u8>(PieceType::NB);
    
    1. O único operador aritmético que seu código de demonstração precisa é incrementar um Square. Isso é fácil de adicionar e, como um operador unário, não há necessidade intrínseca de levar em conta combinações com outros tipos.
    Square& operator++(Square& src) noexcept {
        src = static_cast<Square>(1 + static_cast<u8>(src));
        return src;
    }
    
    1. Embora suas funções getFilee getRanksejam boas, elas são a causa de alguns dos seus problemas. Elas devem ser complementadas por funções paralelas que omitam a conversão final; ou seja, que retornem u8. Eu usaria "Number" como sufixo para indicar que o valor de retorno é um número, adequado para uso em cálculos. Você pode querer modificar getFilee getRankchamar essas novas funções para evitar repetir a aritmética.
    constexpr u8 getFileNumber(Square sq) {
        return static_cast<u8>(sq) % 8;
    }
    
    constexpr u8 getRankNumber(Square sq) {
        return static_cast<u8>(sq) / 8;
    }
    
    1. Os subscritos são um problema mais espinhoso porque operator[]não podem ser definidos fora de uma classe. Então, minha solução seria definir uma classe para encapsular seu array. Você pode descobrir que isso também traz outros benefícios, como a possibilidade de mover precomputeSquareDistancepara o construtor. E não apenas um construtor, mas um constexprconstrutor (os valores podem ser computados durante a compilação em vez de em tempo de execução).
    class Distances {
        std::array<std::array<u8, SquareCount>, SquareCount> squares;
    
        public:
            // Note: C++23 feature used here. For earlier versions, this takes
            // a bit more boilerplate.
            constexpr u8& operator[](Square s1, Square s2) {
                return squares[static_cast<u8>(s1)][static_cast<u8>(s2)];
            }
            // There should also be a `const` version. That complication is routine
            // and left to the reader.
    
            constexpr Distances() {
                for (Square s1 = Square::FIRST; s1 <= Square::LAST; ++s1) {
                    for (Square s2 = Square::FIRST; s2 <= Square::LAST; ++s2) {
                        // Note the comma between the subscripts rather than using
                        // two pairs of square brackets.
                        (*this)[s1, s2] =
                            std::abs(getFileNumber(s1) - getFileNumber(s2)) +
                            std::abs(getRankNumber(s1) - getRankNumber(s2));
                    }
                }
            }
    };
    

    Em conjunto, essas mudanças reduzem as ocorrências de static_castno seu código de demonstração de 25 para 10. O ponto principal é que, além de uma conversão na mainfunção, as conversões foram movidas para funções utilitárias e constantes. As utilidades são o que é usado repetidamente, não static_castdiretamente. Pode haver mais utilidades necessárias no seu código real, mas pode não ser tão ruim quanto você pensava. Principalmente se você parar de dificultar as coisas para si mesmo (veja o nº 3).

    Reconheço a resposta sobre o uso de enumerações sem escopo dentro de um namespace como outra abordagem razoável. Certamente vale a pena considerar se você não conseguir mover conversões suficientes do seu código real para funções utilitárias.


    Para referência, a função principal após a introdução do acima:

    int main() {
        std::cout << static_cast<int>(squareDistance[Square::A1, Square::H8]) << "\n";
        return 0;
    }
    

    O static_castque permanece nesta função é um artefato do uso uint8_te não está relacionado ao uso de enumerações com escopo.

    • 4
  2. Erdal Küçük
    2025-04-25T08:13:54+08:002025-04-25T08:13:54+08:00

    Como @NathanOliver sugeriu nos comentários acima, use enumerações sem escopo dentro de um namespace. Valores de enumerações sem escopo podem ser promovidos ou convertidos para tipos integrais. O contrário pode ser feito usando static_cast.

    Com isso em mente, você pode fazer o seguinte:

    //enum class File : u8
    namespace File {
        enum value : u8 {/* ... */}; //name is optional
    }
    
    //enum class Square : u8
    namespace Square {
        enum value : u8 {/* ... */}; //name is optional
    
        //to circumvent the "ugliness" below
        //and if we're going to use auto, 
        //then we wouldn't have to name the enum (if desired)
        constexpr auto toFile(auto sq) {
            return sq % 8;
        }
    }
    
    //this might look a bit ugly
    //if we were dealing with unnamed enums, 
    //then we would have to use the underlying type (u8 or auto) instead,
    //but then, function overloading would go out of the window
    constexpr File::value getFile(Square::value sq) {
        return static_cast<File::value>(sq % 8); //int to enum: need static_cast here
    }
    
    //but this looks much better (some gain, some loss)
    std::array<std::array<u8, Square::NB>, Square::NB> squareDistance{};
    

    Acho que isso deve cobrir a ideia. Lembre-se de que a promoção e a conversão para o tipo integral são implícitas. A conversão para um valor enum pode ser feita explicitamente usando a static_cast(desde que não exceda o intervalo de valores, UB caso contrário).

    • 0
  3. user30329099
    2025-04-25T05:37:44+08:002025-04-25T05:37:44+08:00
    1. use função auxiliar ou macros: defina função utilitária para encapsular a conversão:

      cpp

      constexpr unit8_t to_unit8(valor enumClass)

      static_cast<unit8_t>(value)
      
    • -1

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve