0

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?

Community
  • 1
  • 1
Tetix
  • 317
  • 2
  • 10
  • Put each constant in a separate header file. – melpomene May 06 '19 at 00:49
  • Can you use C++17? If so then declare them as `inline constexpr` in the header file – NathanOliver May 06 '19 at 00:50
  • @melpomene I am not sure I understand. NathanOliver, I am mainly looking for a C++11 way that makes sense, since I am still learning. Some C++17 stuff are hard to understand why/when they work. – Tetix May 06 '19 at 01:41
  • `enum { WIDTH = 800, HEIGHT = 600 };` – M.M May 06 '19 at 02:47
  • In `width.h`: `namespace constants { const int width = 800; }` In `height.h`: `namespace constants { const int height = 600; }` Now if you change the value of `constants::width`, only files that `#include "width.h"` need to be recompiled. – melpomene May 07 '19 at 04:31
  • @melpomene thank you, but that's almost the same thing, since 99% of files that need access to width also need access to height and vice versa. – Tetix May 07 '19 at 14:16
  • Then what is your actual question? You wrote "*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*", but now you're saying (nearly) every file uses every constant anyway, so why are you worrying about rebuilds? – melpomene May 07 '19 at 18:18
  • @melpomene I don't understand your question neither your comments. I am stating that including a .h as you said, compiling all project, then changing a variable in .h and recompiling will lead to recompilation of whole project and I want to avoid that. I want to recompile only a .cpp ideally. – Tetix May 07 '19 at 19:53
  • Are you saying you magically want to change the size of an object, but without recompiling the code? – melpomene May 07 '19 at 21:00

1 Answers1

1

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;
Walter
  • 44,150
  • 20
  • 113
  • 196
  • 1
    This has exactly the problems described in the "big" paragraph, no? – Tetix May 06 '19 at 01:33
  • I appreciate your time and effort. Although, if I put that code in `globals.h` and include it in multiple .cpps, then all of them will have to be recompiled every time the header file is changed (as the problem described in the big paragraph). Also, `multiple definitions confusing the linker` in my "extern" approach is not true, since that would compile fine. The problem is that std::array expects a compile-time constant and WIDTH and HEIGHT are compile-time constants *only* in the file they are defined (`main.cpp'). – Tetix May 06 '19 at 15:21
  • 1
    Finally, not being able to be used in simple cases such as operator<< pre-C++17 is something that does not look good on its own. – Tetix May 06 '19 at 15:25
  • @Tetix. The solution I have proposed is the standard technique used for this purpuse, including by the C++ standard library itself (e.g. in [`std::numeric_limits<>`](https://en.cppreference.com/w/cpp/types/numeric_limits) with its member constants). The issue with odr-use can be solved (by providing a definition somewhere) and is not a problem. – Walter May 06 '19 at 15:46
  • 1
    Changing the value of a constant in `globals.h` leads to recompiling every single file of the project essentially. There is no way around that (e.g. using a .cpp in parallel)? – Tetix May 06 '19 at 15:51
  • Changing something on which everything else depends requires recompiling everything. Obviously. Trivially. So don't change that too often :-) – Walter May 06 '19 at 15:52
  • Also, due to this, I am not convinced that we don't have two different constants defined in the two files (main.cpp and classA.cpp) – Tetix May 06 '19 at 15:53
  • @Tetix I don't get your "two different constants" problem. If you only ever `#include globals.h` and use the `constants::width` and `constants::height` declared there, then how can you ever get different values??? – Walter May 06 '19 at 15:55
  • A const int 800 for width at main.cpp and a different const int 800 for classA.cpp. Isn't that possible to be happening? I am totally confused with this problem. – Tetix May 06 '19 at 16:05
  • Just don't declare/define any width/height **anywhere** else (not in `main.cpp` and not in `classA.cpp`) but only once in `globals.h` and `#include` that file whenever you need those constants. – Walter May 06 '19 at 16:27
  • https://stackoverflow.com/a/15235626/11231203 As you can see here,`static` should be avoided in header files, because it creates multiple instances of the same variable in each translation unit. – Tetix May 07 '19 at 01:56
  • @Tetix Perhaps you should first properly learn the language before you start coding too complex things. The post you linked unfortunately was incomplete, as it didn't cover `constexpr static` data members. Such members are compile-time constants and have no storage. That's why you cannot odr-use them. – Walter May 07 '19 at 10:10