9

I have a global array, which is indexed by the values of an enum, which has an element representing number of values. The array must be initialized by a special value, which unfortunately is not a 0.

enum {
  A, B, C, COUNT
};

extern const int arr[COUNT];

In a .cpp file:

const int arr[COUNT] = { -1, -1, -1 };

The enum is occasionally changed: new values added, some get removed. The error in my code, which I just fixed was an insufficient number of initialization values, which caused the rest of the array to be initialized with zeroes. I would like to put a safeguard against this kind of error.

The problem is to either guarantee that the arr is always completely initialized with the special value (the -1 in the example) or to break compilation to get the developers attention, so the array can be updated manually.

The recent C++ standards are not available (old ms compilers and some proprietary junk). Templates can be used, to an extent. STL and Boost are strongly prohibited (don't ask), but I wont mind to copy or to reimplement the needed parts.

If it turns out to be impossible, I will have to consider changing the special value to be 0, but I would like to avoid that: the special value (the -1) might be a bit too special and encoded implicitly in the rest of the code.

I would like to avoid DSL and code generation: the primary build system is jam on ms windows and it is major PITA to get anything generated there.

fork0
  • 3,401
  • 23
  • 23
  • 3
    Can you change `arr` from type `const int[COUNT]` to a class type? – aschepler Jun 05 '13 at 19:15
  • 1
    @aschepler No. The values must be known in compile time. No runtime initialization possible, if you wanted to suggest that. – fork0 Jun 05 '13 at 19:21
  • http://stackoverflow.com/questions/2978259/programmatically-create-static-arrays-at-compile-time-in-c – Ryan Haining Jun 05 '13 at 19:35
  • 1
    @RyanHaining no C++0x here. The answer to that question uses variadic template arguments. – fork0 Jun 05 '13 at 19:37
  • "STL and Boost are strongly prohibited " - don't solve the small problems like this, tackle the real problems. And that real problem here is not even that STL is prohibited, but the person who decided that. – MSalters Jun 06 '13 at 23:47
  • 3
    @MSalters: in the school, when you had a problem to solve, did you also "improved its constraints" to make the problem be more worthy a solution? – fork0 Jun 07 '13 at 06:50

5 Answers5

8

The best solution I can come up with is to replace arr[COUNT] with arr[], and then write a template to assert that sizeof(arr) / sizeof(int) == COUNT. This won't ensure that it's initalized to -1, but it will ensure that you've explicitly initialized the array with the correct number of elements.

C++11's static_assert would be even better, or Boost's macro version, but if you don't have either available, you'll have to come up with something on your own.

Collin Dauphinee
  • 13,664
  • 1
  • 40
  • 71
  • 3
    A cheap C++98 `static_assert`: `extern int array_size_check[ sizeof(arr)/sizeof(*arr) == COUNT ? 1 : -1 ];` – aschepler Jun 05 '13 at 19:24
  • I realize that this is damn unfair, but I made a mistake when stating the question (which I just corrected): the array is referenced externally. It is not static, i.e. local to this compile unit. – fork0 Jun 05 '13 at 19:27
  • 1
    @fork0: That shouldn't make a difference if `COUNT` is the same across all TU. – Benjamin Bannier Jun 05 '13 at 19:29
  • @honk: I afraid it does: some of the external code uses sizeof(arr)/sizeof(*arr) instead of COUNT, IOW it actually depends on the declared size of the array. – fork0 Jun 05 '13 at 19:45
  • @fork0: You are right, one cannot have an `extern int arr[]` since it is an incomplete type. – Benjamin Bannier Jun 05 '13 at 19:48
  • But I wonder if I can put the initializers in a macro and count them using the cheap static_assert above: `#define INITIALIZERS -1, -1, -1, ...\n struct a{a(){ int tmp[] = INITIALIZERS; array-size-check for tmp }};` – fork0 Jun 05 '13 at 19:50
5

This is easy.

enum {
  A, B, C, COUNT
};
extern const int (&arr)[COUNT];

const int (&arr)[COUNT] = (int[]){ -1, -1, -1};

int main() {
   arr[C];
}

At first glance this appears to produce overhead, but when you examine it closely, it simply produces two names for the same variable as far as the compiler cares. So no overhead.

Here it is working: http://ideone.com/Zg32zH, and here's what happens in the error case: http://ideone.com/yq5zt3

prog.cpp:6:27: error: invalid initialization of reference of type ‘const int (&)[3]’ from expression of type ‘const int [2]’

For some compilers you may need to name the temporary

const int arr_init[] = { -1, -1, -1};
const int (&arr)[COUNT] = arr_init;

update

I've been informed the first =(int[]){-1,-1,-1} version is a compiler extension, and so the second =arr_init; version is to be preferred.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Oh, even if it had the overhead, it would be negligible. – fork0 Jun 07 '13 at 18:20
  • I think I'm going to accept this as the solution: it is simple, minimal and solves the described problem. Unless someone comes up with a solution to directly initialize the array with the correct amount in the next minutes, that is... – fork0 Jun 07 '13 at 18:30
  • Pity I can't use it by now: the array has been moved into a structure, where it is to be initialized now: `struct abc { int a, b; int c[COUNT]; };`. So I still have to go with the counting of initializers, but it does not matter for this question anymore. – fork0 Jun 07 '13 at 18:33
  • @fork0: amusingly, when I saw your first comment, I was able to elide the named temporary :D But yeah, if it's a member, you'd have to use one of the other answers here. – Mooing Duck Jun 07 '13 at 18:35
  • BTW, you have to revert the solution to the previous version for the compilers before C++11: `prog.cpp:6: error: invalid initialization of non-const reference of type 'const int (&)[3]' from a temporary of type 'int [3]' ` – fork0 Jun 07 '13 at 18:50
2

Answering my own question: while it seems to be impossible to provide the array with the right amount of initializers directly, it is really easy to just test the list of initializers for the right amount:

#define INITIALIZERS -1, -1, -1,
struct check {
  check() {
    const char arr[] = {INITIALIZERS};
    typedef char t[sizeof(arr) == COUNT ? 1: -1];
  }
};

const int arr[COUNT] = { INITIALIZERS };

Thanks @dauphic for the idea to use a variable array to count the values.

Community
  • 1
  • 1
fork0
  • 3,401
  • 23
  • 23
1

The Boost.Preprocessor library might provide something useful, but I doubt whether you will be allowed to use it and it might turn out to be unwieldy to extract from the Boost sources.

This similar question has an answer that looks helpful: Trick : filling array values using macros (code generation)

Community
  • 1
  • 1
Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
  • I looked at it actually before asking. I doubt I will be able to find that "something" in the library. Without help, that is – fork0 Jun 05 '13 at 20:10
  • 1
    Updated my answer with a useful link. From StackOverflow ;-) – Nicola Musatti Jun 05 '13 at 20:17
  • I'll have to try that! Can't do that right now, but I certainly will. – fork0 Jun 05 '13 at 20:25
  • I don't think it can work with compile time constants. Besides, it relies on variadic macro arguments, which is a feature I cannot rely upon to be present in the compilers at my disposition. But I'm going to remember this just for how cool it is, so thanks anyway! – fork0 Jun 07 '13 at 06:54
0

The closest I could get to an initialization rather than a check is to use a const reference to an array, then initialize that array within a global object. It's still runtime initialization, but idk how you're using it so this may be good enough.

#include <cstring>

enum {A, B, C, COUNT};

namespace {
    class ArrayHolder {
        public:
            int array[COUNT];  // internal array
            ArrayHolder () {
                // initialize to all -1s
                memset(this->array, -1, sizeof(this->array));
            }
    };

    const ArrayHolder array_holder; // static global container for the array
}


const int (&arr)[COUNT] = array_holder.array;  // reference to array initailized
                                               // by ArrayHolder constructor

You can still use the sizeof on it as you would before:

for (size_t i=0; i < sizeof(arr)/sizeof(arr[0]); ++i) {
        // do something with arr[i]
}

Edit If the runtime initialization can never be relied on you should check your implementation details in the asm because the values of arr even when declared with an initializer may still not be known at until runtime initialization

const int arr[1] = {5};

int main() {
    int local_array[arr[0]]; // use arr value as length
    return 0;
}

compiling with g++ -pedantic gives the warning:

 warning: ISO C++ forbids variable length array ‘local_array’ [-Wvla]

another example where compilation actually fails:

const int arr1[1] = {5};
int arr2[arr1[0]];

error: array bound is not an integer constant before ']' token

As for using an array value as a an argument to a global constructor, both constructor calls here are fine:

// [...ArrayHolder definition here...]
class IntegerWrapper{
    public:
        int value;
        IntegerWrapper(int i) : value(i) {}
};


const int (&arr)[COUNT] = array_holder.array;

const int arr1[1] = {5};

IntegerWrapper iw1(arr1[0]); //using = {5}
IntegerWrapper iw2(arr[0]);  //using const reference

Additionally the order of initalization of global variables across different source files is not defined, you can't guarantee the arr = {-1, -1, -1}; won't happen until run time. If the compiler is optimizing out the initialization, then you're relying on implementation, not the standard.

The point I really wanna stress here is: int arr[COUNT] = {-1, -1, -1}; is still runtime initialization unless it can get optimized out. The only way you could rely on it being constant would be to use C++11's constexpr but you don't have that available.

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • I don't think it can ever work. The runtime initialization sequence is not defined and cannot be relied upon. What if something else uses the values of `arr` in its global constructor? The constraint "no runtime initialization" is the crucial one. – fork0 Jun 07 '13 at 06:47
  • I think you're completely wrong regarding the order of initialization. And have you tested your code at all? – fork0 Jun 07 '13 at 18:24
  • @fork0 Yeah, I tested, and all were compiled using `-pedantic`. According to this: http://stackoverflow.com/questions/1271248/c-when-and-how-are-c-global-static-constructors-called I missed the point that initializations from contstant expressions happen before dynamic initialization (constructors) occur. However, indexing into something initialized by a const expression does not yield a constant expression itself because the contents of the array won't exist until initialization. – Ryan Haining Jun 07 '13 at 20:54
  • @fork0 I'm not insisting that the reference solution is sufficient, but I am pointing out that if you really can't have any runtime initialization, you already have a problem. But since you are saying a constant expression initialization works for you, I would like to know the actual constraints of the situation. I'm very interested in this question. – Ryan Haining Jun 07 '13 at 21:16
  • the constraints are already stated: the array is referenced from outside. It does not matter in what time during process initialization, but it can be referenced from any initialization routine: global constructor or a function call. – fork0 Jun 08 '13 at 18:43
  • @fork0 what do you mean by "a function call." as an initialization routine? – Ryan Haining Jun 08 '13 at 19:40
  • `int func() { return arr[COUNT-1]; } int var = func();` – fork0 Jun 08 '13 at 20:25
  • But this `int var = arr[COUNT-1];` is just as likely to be present in my situation. Yes, I know it is the same. – fork0 Jun 08 '13 at 20:32
  • @fork0 If these appear in a different source file than the initialization of `arr` they can fail because the order of initialization is not specified across source file boundaries. I guess "fail" isn't exactly true. The outcome isn't well-defined. – Ryan Haining Jun 09 '13 at 06:58