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.