6

The code below demonstrates a behavior of gcc 4.6.2 that I can't account for. The first function declares a static array of type vec_t, where vec_t is a typedef'd alias for unsigned char. The second function is identical, except that the type of vect_t is a template parameter. The second function fails to compile with diagnostic "error: storage size of ‘bitVec’ isn’t constant".

#include <limits>

void bitvec_func()
{
    const std::size_t       nbits = 1e7;
    typedef unsigned char   vec_t;
    const std::size_t       WLEN  = std::numeric_limits<vec_t>::digits;
    const std::size_t       VSIZ  = nbits/WLEN+1;
    static vec_t            bitVec[nbits/VSIZ];    // Compiles fine
}

template <typename T>
void bitvec_func()
{
    const std::size_t       nbits = 1e7;
    typedef T               vec_t;
    const std::size_t       WLEN  = std::numeric_limits<vec_t>::digits;
    const std::size_t       VSIZ  = nbits/WLEN+1;
    static vec_t            bitVec[nbits/VSIZ];    // "error: storage size of ‘bitVec’ isn’t constant"
}

void flarf()
{
    bitvec_func();
    bitvec_func<unsigned char>();
}

It seems to me that instantiating the template with argument <unsigned char> should cause the compiler to generate the same code as the first function. Can anyone offer any insight into why this does not seem to be the case?

[Addendum: the second function will compile with "-std=c++0x" or "-std=gnu++0x", but I'd still like to understand how/if it's wrong under the earlier language definitions.]

ETA:
The second function will compile if the initializer for nbits is changed:

const std::size_t       nbits = 1e7;              // Error
const std::size_t       nbits = (std::size_t)1e7; // Okay
const std::size_t       nbits = 10000000.0;       // Error
const std::size_t       nbits = 10000000;         // Okay

In other words, it seems that if nbits is initialized with an expression of an integral type, then nbits is treated as a constant in the definition of bitVec. If nbits is instead initialized with a floating-point expression, the compiler no longer sees it as constant in the expression for the dimension of bitVec, and the compilation fails.

I'm a lot less comfortable calling "compiler bug" in C++ than I would be in C, but I can't think of any other reason that the above 4 cases would not be semantically identical. Anyone else care to opine?

John Auld
  • 476
  • 2
  • 12
  • Could you post the exact code which is giving you a compiler error? I can't seem to reproduce it. – Jesse Good Jun 06 '12 at 23:23
  • That is the exact code above. Compiler is gcc 4.6.2 and the options are "-O0 -g3 -c". – John Auld Jun 06 '12 at 23:26
  • 1
    On an older gcc 4.3.4, this code [compiled fine](http://ideone.com/65Cl7). – Jesse Good Jun 06 '12 at 23:30
  • Interesting. I copied your code verbatim and reproduced the error on 4.6.2. Perhaps it's a relatively new "feature" :) Thanks for checking. – John Auld Jun 06 '12 at 23:33
  • Sorry I didn't realize before, but what are you trying to do with `nbits = 1e7;`? If you are trying to assign a hex value to an unsigned int, that won't work (also, I think that is the problem). – Jesse Good Jun 07 '12 at 00:18
  • @JesseGood: `1e7` is a floating-point constant, equivalent to `10000000.0` – Chris Dodd Jun 07 '12 at 00:23
  • 10000000 is what was intended. Initializing nbits with the floating-point expression 1e7 seemed harmless, and more legible (it never occurred to me that it might be mistaken for a malformed hex constant :-) ). – John Auld Jun 07 '12 at 01:38

2 Answers2

6

After compiling your code with -ansi on gcc 4.7.0, I was able to reproduce this warning:

warning: ISO C++ forbids variable length array 'bitVec' [-Wvla]

This warning appeared for both bitVec, not just the one in the template function. I then realized that the line nbits = 1e7; is assigning a double to an unsigned int. I think because of this, for some reason causes nbits to not be a constant expression. The reason your code is compiling for the non-templated version is because of the variable length array extension for gcc. Also, your version of gcc for some reason doesn't allow variable length arrays in function templates. To fix your code change 1e7; to 10000000.

EDIT

I asked another question concerning the rule. The answer is in C++03 the code is invalid, but in C++11 it is okay.

Community
  • 1
  • 1
Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • it's not losing it's `const` attribute, but the (implied) `constexpr` attribute. – Mooing Duck Jun 07 '12 at 00:43
  • @MooingDuck: That's true (corrected). However, I still wonder why that is the case. – Jesse Good Jun 07 '12 at 00:47
  • `1e7` and `0x1e7` are very different numbers. `10000000` would be a better substitution. – aschepler Jun 07 '12 at 00:50
  • As mentioned above, 1e7 (10,000,000) is what was intended. It does seem to be the crux of the matter. IANAL, but my understanding is that `int i = 123.4` would convert the floating-point constant to int and use the result to initialize `i`. However, changing the "`nbits`" initializer from `1e7` to `10000000` or even `(std::size_t)1e7` eliminates the error. – John Auld Jun 07 '12 at 02:10
  • @JohnA: See the link in my answer. In C++03, `int i = 123.4` is invalid, but in C++11 the rules changed and it is okay. – Jesse Good Jun 07 '12 at 21:20
0

Surely the answer is that at the stage of compilation where that error is generated, the storage size of the array index is not constant -- i.e. it's upstream of template expansion. Dynamic arrays are not part of C++ 98/03, they're a gcc extension (to C, originally). So the error is in fact correct, even if the implementation looks weird. Presumably GCC hits a standards compliance path for templated arrays, supporting them where required and tossing an error otherwise, but arrays of static types hit the "C" path and thus pick up the gcc extension automatically.

Andy Ross
  • 11,699
  • 1
  • 34
  • 31
  • 1
    The code doesn't actually have variable arrays, so the error is _wrong_. The size of the array is known at compile time, and should be treated as such by a conforming compiler. – Mooing Duck Jun 06 '12 at 23:43
  • It's not simply an issue of runtime variability. I believe (but am too lazy to check), that C++03 disallows template parameters specifically in array size initializers (precisely because of the chicken-and-egg issue with getting them expanded at the right time). No doubt someone with better knowledge of the spec will come along to correct me. – Andy Ross Jun 07 '12 at 00:25
  • Interesting. This (mess) works: `template void a() { typedef T vec_t; static vec_t bitVec[sizeof(vec_t)*8]; }` – John Auld Jun 07 '12 at 01:48