Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing enum-indexed array?

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?

like image 953
SF. Avatar asked Nov 17 '11 12:11

SF.


People also ask

Can I use enum as array index?

In C++, I can define a enum and use the enum as the array index.

How do you initialize an entire array with value?

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.

How do you initialize an array?

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.


3 Answers

#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;
    }
}
like image 185
Cheers and hth. - Alf Avatar answered Nov 15 '22 21:11

Cheers and hth. - Alf


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.

like image 27
Flexo Avatar answered Nov 15 '22 19:11

Flexo


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.

like image 21
SF. Avatar answered Nov 15 '22 20:11

SF.