Declare an array in header that used global variable as their elements? - c++

I try to use a global variable as elements for an array.
The problem is that the compiler want to know the integer constant value while declaring an array. I thought that my global variable is allready a const int???
global header file "constants.h"
#ifndef CONSTANT_H
#define CONSTANT_H
namespace constants{
extern const int MY_ROW;
extern const int MY_COLUMN;
}
#endif // CONSTANT_H
define global "constants.cpp"
#include "constants.h"
namespace constants{
const int MY_ROW{55};
}
main function "main.cpp"
#include "constants.h"
#include <iostream>
#include <array>
int main()
{
std::cout<<constants::MY_ROW<<std::endl;
int my_int_array[constants::MY_ROW];
return 0;
}
So far everything is going well, I can declare an array in main using globals as elements.
But if I try "the same thing" in another header the compiler complains.
"test.h"
#ifndef TEST_H
#define TEST_H
#include "constants.h"
class Test
{
public:
Test();
~Test();
void display_array();
private:
int test_array[constants::MY_ROW]; //error here???
};
#endif // TEST_H
error message:
**error: array bound is not an integer constant before ']' token|**
I appreciate it if someone can bring light to darkness.

In main(), for int my_int_array[constants::MY_ROW]; the value of MY_ROW is not known to the compiler, as it is not resolved until the linker stage, so my_int_array cannot be allocated at compile-time, only at runtime, and only if you are using a compiler that supports Variable Length Arrays (see Why aren't variable-length arrays part of the C++ standard?).
But for class Test, that is simply not an option. You can't use a VLA in a class, since the compiler needs to know up front the full size of all members in order to setup memory storage for instances of the class. But the size of the array is not know to the compiler, so the class cannot be compiled.
To do what you are attempting, you need to initialize the constants directly in their declarations, not split them up with extern, eg:
#ifndef CONSTANT_H
#define CONSTANT_H
namespace constants{
const int MY_ROW = 55;
const int MY_COLUMN = ...;
}
#endif // CONSTANT_H
See Define constant variables in C++ header

As noted by Remy, the issue is that the compiler can't see the value of MY_ROW when main.cpp or any file
#includeing test.h is compiled.
If you're using C++17, there's a fairly elegant fix for this. The goal is to make the value MY_ROW visible when main.cpp is compiled, so we need to move the definition of MY_ROW to constants.h:
#ifndef CONSTANT_H
#define CONSTANT_H
namespace constants{
constexpr int MY_ROW{55};
// ...
}
#endif // CONSTANT_H
Note that I swapped const with constexpr to ensure that MY_ROW is a compile time constant rather than a runtime constant.
This version of constants.h will allow your program to compile, but its still not ideal. The issue is that every file that includes constants.h will receive its own MY_ROW definition with static linkage. This means that we may end up with multiple MY_ROWS being stored in the final executable, all with different addresses.
Luckily, in C++17, inline variables were introduced, which essentially let us provide multiple definitions for a single variable with external linkage:
#ifndef CONSTANT_H
#define CONSTANT_H
namespace constants{
inline constexpr int MY_ROW{55};
// ...
}
#endif // CONSTANT_H
With this implementation, you are free to include constants.h in multiple translation units and the linker will ensure that only one MY_ROW makes it to the final executable. You are also now free to use MY_ROW wherever a compile time constant is needed, so your declarations of my_int_array in main.cpp and test_array in test.h would now be valid.

Related

How to use non-const global variable that defined in a namespace

I have an header file that i put some global const variables. Now I need another project wide modifiable variable. But it gives linker error as expected. I am trying to solve the issue without using inline keyword. My codes:
constants.h:
#ifndef CONSTANTS_H
#define CONSTANTS_H
namespace constants {
bool myVar;
}
#endif // CONSTANTS_H
I am using this variable in my classes with constants::myVar. And I got a linker error. Any solution without using inline (for backward compiler compability)?
I completely agree with the comments above about looking at alternatives for this design.
Also as you seems to be aware, c++17 inline variables offer a better solution.
Having said that, if you must keep the current design - you can do the following:
Change the header file to declare myVar as extern:
namespace constants {
//--vvvvvv------------
extern bool myVar;
}
Add constants.cpp file (if it doesn't exists yet), with a definition for myVar (preferably with some initialization):
namespace constants {
bool myVar;
// or better: something like:
bool myVar = false;
}

How can I declare a global compile-time constant for std::array?

I am trying to implement a global variable that will be able to be used by different files and at the same time with std::array, but I get the following compiler error:
error: the value of ‘constants::HEIGHT’ is not usable in a constant expression
note: ‘constants::HEIGHT’ was not initialized with a constant expression
My code is split in the following files at the moment:
main.cpp
#include <iostream>
#include "classA.h"
#include "globals.h"
namespace constants {
extern const int WIDTH = 800;
extern const int HEIGHT = 600;
}
int main()
{
ClassA classA;
printf("Hello World");
std::cout << constants::WIDTH << " " << constants::HEIGHT << std::endl;
return 0;
}
classA.h
#include <array>
#include "globals.h"
class ClassA {
public:
std::array<int, constants::HEIGHT> m_arrayFixedRGBA;
ClassA();
};
classA.cpp
#include "classA.h"
ClassA::ClassA() {
}
globals.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
namespace constants {
extern const int WIDTH;
extern const int HEIGHT;
}
#endif
I know that by removing extern, declaring the values in globals.h like this
#ifndef CONSTANTS_H
#define CONSTANTS_H
namespace constants {
const int WIDTH = 800;
const int HEIGHT = 600;
}
#endif
and removing the relevant lines in main.cpp, then the program is able to compile.
While this is simple (and fine for smaller programs), every time globals.h gets #included into a different code file, each of these variables is copied into the including code file. Therefore, if globals.h gets included into 20 different code files, each of these variables is duplicated 20 times. Header guards won’t stop this from happening, as they only prevent a header from being included more than once into a single including file, not from being included one time into multiple different code files. This duplication of variables isn’t really that much of a problem (since constants aren’t likely to be huge), but changing a single constant value would also require recompiling every file that includes the constants header, which can lead to lengthy rebuild times for larger projects.
What could be a work-around for this scenario?
You could define your constants as static constexpr members
// in some header file:
struct constants {
constexpr static int width = 800;
constexpr static int height = 600;
};
and use them like
std::array<int, constants::height> arr;
------ EDIT ------
Note that this approach only declares these compile-time constants, but does not define any variable. Thus there is no problem with multiple definitions confusing the linker (as with your implementation using extern variables).
However, before C++17 the opposite problem can occur: if you odr-use these constants, a link-time error will arise, as the linker cannot find a definition. For example the following code will fail
std::cout << constants::width << std::endl;
since operator(std::ostream&, something const&) takes the object to be written by reference. You can avoid this by either providing a definition somewhere (in a source file), or by avoiding such use, e.g.
std::cout << int(constants::width) << std::endl;

Why can't I just define a non-const gloabal variable in header? and if I use namespaces why do I have to declare it 'extern'?

1) I know a non-const variable is external linkage by default (it's like it's been declared as external more or less) but i don't understand why can't i define a global variable such as int glbl_a in header
//test.h
#ifndef TEST_H
#define TEST_H
int glbl_a=0; //wrong -> "multiple definition of `glbl_a`"
static int st_glbl_a=1; //ok to initialize it in header!
extern int ext_glbl_a; //ok to declare it and define it in test.cpp
#endif
//test.cpp
#include "test.h"
using namespace std;
//st_glbl_a=22; i could use st_glbl_a but it wouldn't affect st_glbl_a in main cause of 'static'
int ext_glbl_a=2; //definition of external gloabal non-const variable
//main.cpp
#include <iostream>
#include "test.h"
using namespace std;
extern int glbl_a; //declaration of glbl_a external
int main(){
cout<<glbl_a;
}
the working version for this program is the one in which I define int glbl_a=0; in test.cpp only and declare extern int glbl_a; in main before using it in output (definition in test.h is just commented, that is there's nothing about glbl_a).
2)the working version doesn't work anymore if I group all definitions/declaretions into a namespace spread onto test.cpp and test.h (MyNamespace) cause of int glbl_a in test.cpp:
//test.h
#ifndef TEST_H
#define TEST_H
namespace MyNamespace{
//extern int glbl_a;
}
#endif
//test.cpp
#include "test.h"
using namespace std;
namespace MyNamespace{
int glbl_a=0;
}
//main.cpp
#include <iostream>
#include "test.h"
using namespace std;
int main(){
cout<<MyNamespace::glbl_a; //wrong -> "'glbl_a' is not a member of 'MyNaspace'"
}
it would work only if I de-comment declaration in test.h, but why?
Problem 1
Including a header effectively pastes the included file into the including file, producing one large file that is then compiled (and, typically, promptly deleted). This means that every including file now has its very own glbl_a. The compiler is happy, but when the linker attempts to put everything together, it finds many equally valid pretenders to the name glbl_a. The linker hates this and doesn't even try to figure out what you're trying to do. It simply spits out an error message and asks that you fix the problem.
Problem 2
test.cpp and main.cpp are different translation units. They compile to produce different, completely independent objects. Neither can see what's in the other, so the fact that MyNamespace::glbl_a exists in test.cpp is lost on main.cpp. When main.cpp is compiled, the compiler builds a list of identifiers declared in the file constructed from main.cpp and all of its included headers. MyNamespace::glbl_ais never declared as of when it is first used (or after for that matter) so the compiler spits out an error message.
Uncommenting the declaration in test.h means the compiler will find MyNamespace::glbl_a in main.cpp and will allow it's use. Since MyNamespace::glbl_a is defined in test.cpp the linker can find one-and-only-one MyNamespace::glbl_a and can successfully link.
extern does not allocate storage. Instead it is a promise to the compiler that the variable being declared will be fully defined and allocated somewhere else, maybe later in the file or in another file. The variable exists, somewhere, and compilation can continue. The linker will call you out as a liar if it cannot find a definition.
More details here: How does the compilation/linking process work?
More on extern: When to use extern in C++ and Storage class specifiers
headers will be included by other files (more than one) thus if you define in header, it will be in each translation unit thus lead to "multiple definition"

How to use a C++ extern constant variable for a template argument in a different file

I have the following 5 files: global_vars.h, global_vars.cpp content.h content.cpp main.cpp.
global_vars.h
#ifndef global_vars_h
#define global_vars_h
namespace Constants{
extern const unsigned int b;
}
#endif
global_vars.cpp
#include "global_vars.h"
namespace Constants{
extern const unsigned int b(5);
}
content.h
#ifndef CONTENT_H_
#define CONTENT_H_
#include "global_vars.h"
#include <bitset>
struct a{
std::bitset<Constants::b> s;
int a=10;
};
#endif
content.cpp
#include "content.h"
a xVar;
main.cpp
#include "content.h"
int main(){
return 0;
}
I get the following errors:
In file included from content.cpp:1:0:
content.h:11:31: error: the value of ‘Constants::b’ is not usable in a constant expression
In file included from content.h:4:0,
from content.cpp:1:
global_vars.h:6:28: note: ‘Constants::b’ was not initialized with a constant expression
extern const unsigned int b;
I have to use Constants::b in files other than content.cpp/.h (for other bitsets) as well so how can I go around doing this? Appreciate the help.
thanks
What you ask is not possible.
In C++, templates are resolved entirely by the compiler, the linker only gets to see fully instantiated bodies of template classes and functions. The compiler only gets to see code that is in a given compilation unit.
Therefore, even if an int is extern const and assigned a value in some compilation unit (making it valid at run-time), it cannot possibly be used as a template parameter in any other compilation unit. There would be no way for the compiler to know the value of that int at the time that it is resolving which template instantiations refer to the same types.
The closest you can get most likely, is that you could a pointer to that int as a template parameter, and then if you have e.g. a function using that template parameter which is run at run-time, it could dereference the pointer to get the constant value. If you have link-time optimizations enabled it might even inline it, I'm not sure.

global extern const clarification

Is declaring extern const or just extern the same thing in a header file? Also will both give external linkage?
globals.cpp
#include <string>
extern const std::string foo = "bar";
globals.h
#ifndef GLOBALS_H
#define GLOBALS_H
#include <iostream>
extern const std::string foo;
#endif /* GLOBALS_H */
OR
globals.h
#ifndef GLOBALS_H
#define GLOBALS_H
#include <iostream>
extern std::string foo;
#endif /* GLOBALS_H */
Both compiles and run fine, both give same address when used in multiple files, which one is more correct?
This one is correct,
//globals.h
extern const std::string foo; //Consistent
Consistent!
The correct one is
extern const std::string foo;
foo is a constant string in the source file it is defined in, so the extern declaration should indicate this too and allow the compiler to catch instances where code within source file where that header is included may try to modify foo.
The only reason they both compile fine is that you are not actually using foo, so it doesn't matter whether the linker can find foo or not (it never tries to). (Or your compiler/linker is rubbish. :) )
The consistent header is fine. The one missing the const is not and will/should result in errors if something tries to use foo.
If you make some file (not globals.cpp) which includes globals.h and tries to use foo (e.g. just add a line with "foo.c_str();" into a function) then you'll get a link error because no const std::string foo could be found.
This is how it has to be, too. The header is telling anything that wants to use foo that it can only read it, not modify it. Things which include the globals.h header have no idea of what is inside globals.cpp; if you want them to treat an object as const you have to put that information in a file which they include.
You can give const external linkage, as in your example, but it's still non-modifiable (constant).
P.S. you need to include <string>, not <iostream>
P.P.S. If I wasn't clear, you can't redefine const-ness. This will not compile:
extern std::string foo;
extern const std::string foo = "bar";
For C language, declare actual const in a .c file and use 'extern' for it in a header (.h) file. This should be as usual as we do when declaring a global variable.