Hoy reinventé esta rueda y pensé en compartirla.
Esta implementación no requiere ningún cambio en el código que define las constantes, que pueden ser enumeraciones #define
o cualquier otra cosa que se transfiera a un número entero; en mi caso, tenía símbolos definidos en términos de otros símbolos. También funciona bien con valores dispersos. Incluso permite múltiples nombres para el mismo valor, devolviendo el primero siempre. El único inconveniente es que requiere que hagas una tabla de las constantes, que pueden quedar desactualizadas a medida que se agreguen nuevas, por ejemplo.
struct IdAndName
{
int id;
const char * name;
bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }
const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
std::stable_sort(table_begin, table_end);
IdAndName searchee = { id, NULL };
IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
return (p == table_end || p->id != id) ? NULL : p->name;
}
template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
return IdToName(id, &table[0], &table[N]);
}
Un ejemplo de cómo lo usarías:
static IdAndName WindowsErrorTable[] =
{
ID_AND_NAME(INT_MAX), // flag value to indicate unsorted table
ID_AND_NAME(NO_ERROR),
ID_AND_NAME(ERROR_INVALID_FUNCTION),
ID_AND_NAME(ERROR_FILE_NOT_FOUND),
ID_AND_NAME(ERROR_PATH_NOT_FOUND),
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
ID_AND_NAME(ERROR_ACCESS_DENIED),
ID_AND_NAME(ERROR_INVALID_HANDLE),
ID_AND_NAME(ERROR_ARENA_TRASHED),
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
ID_AND_NAME(ERROR_INVALID_BLOCK),
ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
ID_AND_NAME(ERROR_BAD_FORMAT),
ID_AND_NAME(ERROR_INVALID_ACCESS),
ID_AND_NAME(ERROR_INVALID_DATA),
ID_AND_NAME(ERROR_INVALID_DRIVE),
ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
ID_AND_NAME(ERROR_NO_MORE_FILES)
};
const char * error_name = IdToName(GetLastError(), WindowsErrorTable);
La IdToName
función se basa en std::lower_bound
realizar búsquedas rápidas, lo que requiere que se ordene la tabla. Si las dos primeras entradas de la tabla están fuera de servicio, la función lo ordenará automáticamente.
Editar: Un comentario me hizo pensar en otra forma de usar el mismo principio. Una macro simplifica la generación de una gran switch
declaración.
#define ID_AND_NAME(x) case x: return #x
const char * WindowsErrorToName(int id)
{
switch(id)
{
ID_AND_NAME(ERROR_INVALID_FUNCTION);
ID_AND_NAME(ERROR_FILE_NOT_FOUND);
ID_AND_NAME(ERROR_PATH_NOT_FOUND);
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
ID_AND_NAME(ERROR_ACCESS_DENIED);
ID_AND_NAME(ERROR_INVALID_HANDLE);
ID_AND_NAME(ERROR_ARENA_TRASHED);
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
ID_AND_NAME(ERROR_INVALID_BLOCK);
ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
ID_AND_NAME(ERROR_BAD_FORMAT);
ID_AND_NAME(ERROR_INVALID_ACCESS);
ID_AND_NAME(ERROR_INVALID_DATA);
ID_AND_NAME(ERROR_INVALID_DRIVE);
ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
ID_AND_NAME(ERROR_NO_MORE_FILES);
default: return NULL;
}
}