5

I have the following three files in my code (with most of the code removed. This is just to isolate the issue).

global.h:

//global.h
#ifndef GLOBAL_H
#define GLOBAL_H
extern const int ARRAYSIZEX;
extern const int ARRAYSIZEY;
extern const int ARRAYSIZEZ;
#endif //GLOBAL_H

global.cpp:

//global.cpp
#include "global.h"
const int ARRAYSIZEX = 5;
const int ARRAYSIZEY = 2;
const int ARRAYSIZEZ = 4;

main:

//main
#include "global.h"
using namespace std;

someType mySomeTypeArray[ARRAYSIZEX][ARRAYSIZEY][ARRAYSIZEZ];

int main(int argc, char **argv)
{
//...
}

Compiling gives me three errors at the declaration of mySomeTypeArray.

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

I want to keep my global variable and array size definitions in global.h/cpp for this application, just for organization, so that all my configuration parameters are in one place. What's the proper way to achieve what I'm trying to do?

Thanks

boxcartenant
  • 271
  • 2
  • 14
  • The problem is that all your main.cpp sees is the extern declaration, not the actual assignment to a value, so it has no idea how big to make the array. – xaxxon Aug 20 '18 at 23:44
  • If this is C++ then what's going on with these fixed-length arrays? Why not `std::vector`? – tadman Aug 20 '18 at 23:44
  • std::vector has a very different behavior than arrays. There are plenty of reasons not to use vector. – xaxxon Aug 20 '18 at 23:45
  • @xaxxon Thanks for the input! How would you go about structuring an application like this, if you wanted to also make the assignment available to other .cpp files? – boxcartenant Aug 20 '18 at 23:46
  • Using all UPPERCASE compiler identifiers for constants is antipattern. Bad habbits die hard – Slava Aug 21 '18 at 00:41

5 Answers5

4

The problem here is extern int x means "x is defined in another file, but don't worry about the particulars, all you need to know is it's an int". This is normally good enough, except when the compiler needs to know right there and then what x is.

Since that's defined in a whole other file it can't. That file must be compiled before it knows, and the result of that compilation, due to the way C++ works, can't impact the compilation of this file.

You'll need to declare that as a const int in a header if you want to share those values. extern int won't cut it.

Although this is a trivial example, there's really no reason to go down the extern road at all. Just define the values in the header file as regular const int.

tadman
  • 208,517
  • 23
  • 234
  • 262
3

Your declaration is failing because array sizes need to be evaluated at compile-time and your encapsulation scheme is actually hiding the values from the compiler. This is true because compilers work on individual translation units. While compiling main.cpp your compiler sees only extern const int ARRAYSIZEX thanks to the include statement, but not the value which is visible in a separate translation unit so it can't figure out the memory layout.

While const variables can used as array sizes in some contexts, the language provides the more appropriate constexpr qualifier which comes with a set of restrictions that enforce its compile-time evaluation and suitability for array sizes. I recommend always using it when appropriate because it will point you to the error in situations such as this. In this case, you would get a compiler error because an extern constexpr declaration is ill-formed which hints at the proper solution: to hold the values for compile-time constants directly inside the header file.

global.h

constexpr int ARRAYSIZEX = ...;
constexpr int ARRAYSIZEY = ...;
constexpr int ARRAYSIZEZ = ...;

main.cpp

#include "global.h"
someType mySomeTypeArray[ARRAYSIZEX][ARRAYSIZEY][ARRAYSIZEZ];
patatahooligan
  • 3,111
  • 1
  • 18
  • 27
  • I tried this and it worked for the simple case. Will this also give me external linkage, so that I can access the constants in multiple files? – boxcartenant Aug 21 '18 at 00:05
  • 1
    @boxcartenant you do not need external linkage to use the constants in multiple fies – M.M Aug 21 '18 at 00:06
  • @boxcartenant `constexpr` variables are implicitly `inline`, so they are not subject to the one definition rule. As long as every translation unit that defines them uses the exact same definition, you're fine. In short, make sure you define them in exactly one header file and just include that in as many files as necessary. – patatahooligan Aug 21 '18 at 00:11
  • @patatahooligan Wow, thanks guys! I learned a lot about the compiler while googling your answers. Also, the issue has been resolved. I went ahead and replaced a number of similar implementations of extern const with constexpr to try it out, and it seems to work across the board. – boxcartenant Aug 21 '18 at 00:15
  • 1
    `constexpr` variables are implicitly inline only when they're static members. This is currently going to be an ODR violation. – Passer By Aug 21 '18 at 00:46
  • @PasserBy It seems you are right about the `inline` part but I think there is still no ODR violation. According to cppreference a variable is required to have exactly one definition in the entire program if it is ODR-used. Reading the value of a compile time constant does not appear to be an ODR-use. I'll research it more tomorrow and edit this post with any info I can gather. – patatahooligan Aug 21 '18 at 02:17
  • My mistake, the variable will just have internal linkage. – Passer By Aug 21 '18 at 07:10
2

Array size must be specified by an integer constant expression. A const int object can be used in an integer constant expression if and only if it declared with an initializer and that initializer is also an integer constant expression. Your ARRAYSIZE... variables do not satisfy that requirement. In main they are declared without an initializer. You cannot use ARRAYSIZE... variables as array sizes in main.

Unless you have a specific requirement to give these variables external linkage, simply declare (and define) them in the header as

const int ARRAYSIZEX = 5;
const int ARRAYSIZEY = 2;
const int ARRAYSIZEZ = 4;

These object will have internal linkage though, which is different from what your original variant attempts to do.

If really want to give them external linkage, declare them as inline extern const in the header

inline extern const int ARRAYSIZEX = 5;
inline extern const int ARRAYSIZEY = 2;
inline extern const int ARRAYSIZEZ = 4;

Since inline by itself prevents const from imposing internal linkage, extern is entirely optional in these declarations. And since inline const combination can be replaced with constexpr (as @M.M noted in the comments), you can achieve the same effect with just

constexpr int ARRAYSIZEX = 5;
constexpr int ARRAYSIZEY = 2;
constexpr int ARRAYSIZEZ = 4;
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • I tried this and it worked... but I think I don't understand the combination of extern and inline. I thought "extern" means it's defined here and instantiated once in another file, but doesn't "inline" mean that it has to be defined again in each translation unit? (and it's my understanding that each translation unit is a single file). – boxcartenant Aug 21 '18 at 00:03
  • `constexpr` could replace `inline const` – M.M Aug 21 '18 at 00:04
  • 2
    @boxcartenant: No, `extern` means "has external linkage". It is needed to override internal linkage imposed by `const`. Whether it means "instantiated in another file" or "instantiated here" depends on the presence of initializer. If initializer is present, `extern` means "instantiated here and available to everyone else". `inline` is a separate story. It means "despite having external linkage, it is OK to define it multiple times in different translation units", which is what we are essentially doing by defining it in the header file. – AnT stands with Russia Aug 21 '18 at 00:14
  • @AnT Good summary of those functions. I really appreciate your answer! I learned a lot by researching these terms. – boxcartenant Aug 21 '18 at 00:18
  • 1
    @boxcartenant: A correction: `inline` is already enough to prevent `const` from imposing internal linkage in this case. Which means that there's no need to specify `extern` there. It is not an error, but it will work without it as well. – AnT stands with Russia Aug 21 '18 at 00:30
1

Even though inline and constexpr has been discussed in other answers and comments, there is an aspect that is worth highlighting. The following header file would serve the OP's need in an efficient and simple to understand way.

//global.h
#ifndef GLOBAL_H
#define GLOBAL_H
inline constexpr int ARRAYSIZEX = 5;
inline constexpr int ARRAYSIZEY = 2;
inline constexpr int ARRAYSIZEZ = 4;
#endif //GLOBAL_H

This is based on how inline variables (introduced in C++17) work. These inline variables are allowed to be defined once for each translation unit that uses them, as long the definitions are same. Thus, they may be conveniently defined in a header file that is included in multiple source files. Also, these inline variables have external linkage when not additionally declared as static. Thus, these variables have the same address in every translation unit, making this approach efficient.

Also, there are other threads on SO that one can explore to understand this topic further.

Hari
  • 1,561
  • 4
  • 17
  • 26
0

The problem here is that ARRAYSIZEX, ARRAYSIZEY and ARRAYSIZEZ are not the compile time constants. They are constants - so their values can't be changed but their values are not known to compiler.

In C++ the compilation process consists of 3 basic steps.

  1. Preprocessing of all source files done by preprocessor.
  2. Compilation for every translation unit (.cpp file) done by compiler. For every translation unit compiler creates an object file.
  3. Linking of all object files done by linker. The output is an executable file.

In C++ the keyword extern for compiler means that the variable is 'somewhere' defined. The compiler doesn't know the variable's real address but by placing keyword extern it's assured that the variable really exists and the linker will be able to find its address by its name when creating the executable file.

The problem here is that compiler in step 2 wants to create the object file but it doesn't known how big the array is going to be because it doesn't know the value of these constants. Yes, linker in step 3 will finally find them when putting together all object files but it's too late for compiler. So it generates that error.

The solution is simple. Use already mentioned constexpr keyword and initialize all variables with initializers. The keyword constexpr marks compile time constants - constants that have to be initialized in initializers and are known to compiler.

Timmy_A
  • 1,102
  • 12
  • 9