gcc has a very nice extension in C that allows you to keep data in arrays using enum as keys:
enum keys
{
key_alpha = 0,
key_beta = 1,
key_gamma = 2
};
ValType values =
{
[ key_alpha ] = { 0x03b1,"alpha" },
[ key_gamma ] = { 0x03b3,"gamma" },
[ key_beta ] = { 0x03b2,"beta" }
};
This is nice because if the list has to change, adding or removing a line doesn't destroy the assignment, it is obvious which key corresponds to which value, and results in simple code no different from plain standard array initialization.
Unfortunately, this extension is not available in g++.
What would be the preferred lightweight way of doing the same thing in C++? Preferably something not based on <map> and the likes that use string keys, hidden indices, heavy templates or other CPU- and memory-heavy stuff?
In C++, I can define a enum and use the enum as the array index.
int num[5] = {1, 1, 1, 1, 1}; This will initialize the num array with value 1 at all index. The array will be initialized to 0 in case we provide empty initializer list or just specify 0 in the initializer list. Designated Initializer: This initializer is used when we want to initialize a range with the same value.
The initializer for an array is a comma-separated list of constant expressions enclosed in braces ( { } ). The initializer is preceded by an equal sign ( = ). You do not need to initialize all elements in an array.
#include <iostream>
#define KEYS_DEF \
KEY_DEF( alpha, 0x03b1, "alpha" ), \
KEY_DEF( beta, 0x03b2, "beta" ), \
KEY_DEF( gamma, 0x03b3, "gamma" )
#define KEY_DEF( identifier, id, name ) identifier
enum keys { KEYS_DEF };
#undef KEY_DEF
#define KEY_DEF( identifier, id, name ) { id, name }
struct ValType { int id; char const* name; };
ValType const values[] = { KEYS_DEF };
int main()
{
using namespace std;
for( int i = alpha; i <= gamma; ++i )
{
cout << values[i].name << endl;
}
}
I suspect this extension exists precisely because there is no simple, portable way of achieving this behaviour. You can emulate it using something like:
enum keys
{
key_alpha = 0,
key_beta = 1,
key_gamma = 2
};
struct ValType {
int v;
const char *name;
};
template <int key>
struct param;
#define SETPARAM(key,value1,value2) \
template <> \
struct param< (key) > { \
static constexpr ValType t {(value1),(value2)}; \
}
SETPARAM(key_alpha, 0x03b1,"alpha");
SETPARAM(key_gamma, 0x03b3,"gamma");
SETPARAM(key_beta, 0x03b2,"beta");
which is portable and meets your requirements without being particularly "heavy templates".
If you're not using C++11 you can still do this, the macro that specialises the param
template will become slightly longer though.
Modification to make use like int i = someinput(); cout << param<i>::t.name;
legal:
#include <cassert>
enum keys
{
key_alpha = 0,
key_beta = 1,
key_gamma = 2
};
struct ValType {
int v;
const char *name;
};
template <int key>
struct param {
enum { defined = false };
static constexpr ValType t {0, 0};
};
template <int key>
constexpr ValType param<key>::t;
static const int MAXPARAM=255;
#define SETPARAM(key,value1,value2) \
template <> \
struct param< (key) > { \
static_assert(key <= MAXPARAM, "key too big"); \
enum { defined = true }; \
static constexpr ValType t {(value1),(value2)}; \
}; \
constexpr ValType param<(key)>::t
template <int C=0>
struct get_helper {
static const ValType& get(int i) {
return i==0 ? (check(), param<C>::t) : get_helper<C+1>::get(i-1);
}
private:
static void check() {
assert(param<C>::defined);
}
};
template <>
struct get_helper<MAXPARAM> {
static const ValType& get(int) {
assert(false);
}
};
const ValType& GETPARAM(int key) {
return get_helper<>::get(key);
}
The trick is to instantiate get_helper
and recurse through the calls with a flag that can be used to assert the validity of the index. You can increase MAXPARAM
if needed, but it'll make compiling slower.
Example usage is pretty simple still:
#include "enumidx.hh"
#include <iostream>
SETPARAM(key_alpha, 0x03b1,"alpha");
SETPARAM(key_gamma, 0x03b3,"gamma");
SETPARAM(key_beta, 0x03b2,"beta");
int main() {
int key = key_beta;
const ValType& v = GETPARAM(key);
std::cout << v.name << std::endl;
}
To have more than one of these in any given program you could use anonymous namespaces and/or make the name of the base struct
(param
in this example) a macro argument and add another macro STARTPARAM
(?) to define the unspecialised template of that name.
A cheap, sneaky, cheaty solution: define the "values" variable in a separate .c file next to all the .cpp files, define the enum and "extern values" in a .h file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With