0

I'm using a pre-C++11 compiler and I'm trying to "export" a constant, without exposing the classes from which this constant is calculated.

// A.hpp ----------------------
struct A{
...
};
// B.hpp ----------------------
struct B{
...
};

// Manager.hpp ------------------
#include "Manager.hpp"
template <size_t a, size_t b>
struct StaticMax
{
   enum { value = a>b ? a : b };
}
class Manager
{
public:
    static const size_t OverlayDataSize;
 ...
};
// manager.cpp ------------------
#include "A.hpp"
#include "B.hpp"
// I want the following constant to be available to anyone
// that includes Manager.hpp, but the classes A and B should not
// be visible to anyone else except class Manager
const size_t Manager::OverlayDataSize = StaticMax<sizeof(A),sizeof(B)>::value;

// otherfile.hpp -------------------
#include "Manager.hpp"
struct OverlayData
{
    // shared state goes here
    uint8 gameState;
    uint8 specificState[Manager::OverlayDataSize];
};
class NvRam
{
    void Write(OverlayData& serializedState);
    ....
}

The above code won't compile and results in:

error: ‘Manager::OverlayDataSize' is not a valid template argument for type ‘unsigned int’ because it is a non-constant expression

Which is already strange since Manager::OverlaySize most definitely is const and its value is calculated at compile time. But according to this question, if a const declaration and its definition aren't in the same location, then the compiler can't use it as a constant. The error persists even if you use a global variable declared with extern. I could have calculated the maximum size differently by using a union (but then structs A and B aren't allowed to have constructors), but that's not the problem, I still can't export that constant to be available at compile-time without exposing structs A and B to everyone. Of course I can side step the whole issue by making the DataOverlay struct a bit more complicated and using new uint8[Manager::OverlayDataSize]; at runtime and be able to maintain strict seperation. But I'm shooting for this to be done statically at compile time.

So how to "export" a compile-time constant while maintaining strict separation between structs A and B and users of Manager?

jhufford
  • 470
  • 2
  • 10
  • When you use a compile time constant, the compiler should already know its value at that time. So in order to use your `OverlayDataSize`, you must have its definition in scope, and thus requiring `A` and `B` in scope. – Ruifeng Xie Jan 24 '19 at 03:24
  • So basically there's no way to achieve my aims... Seems like this is a hangover from C's header file system or does this have some more specific reason to do with the way the compiler does its business? Since `OverlayDataSize` is calculated at compile time, I see no reason in principle this couldn't be done, but it'd probably require putting in placeholders for the constants and going back over the compiled code and replacing placeholders once the compiler makes the needed calculation... – jhufford Jan 24 '19 at 03:50
  • I'm afraid the compiler just don't work that way. That said, I really had a solution, but it's rather ugly. I'll write an answer for that ugly solution anyway. – Ruifeng Xie Jan 24 '19 at 04:49
  • Are `A` and `B` used for anything else? Given the way the code is set up I expect they are, but in that case I'm not sure why you want to go to the effort of hiding them when whatever included `"Manager.hpp"` could also just include `"A.hpp"` and `"B.hpp"`. If they aren't, you can make them internal `private` member classes of `Manager`. – Daniel H Jan 24 '19 at 07:30
  • I agree Daniel, it's not worth the trouble to hide them. Maybe just put them in a namespace and call it good. – jhufford Jan 24 '19 at 08:06
  • In my actual design, A and B are already inner classes of other objects, those classes store their authoritative minimalist state in A & B with the outer classes doing the work... It's actually the outer classes I want to hide. I just simplified it in the example. I think the only way to truly make them hidden would be like Daniel said, make them private inner classes of Manager, but then there would probably need to be `friend` somewhere in there, it starts to be an abuse of the class system. Just a consequence of C++'s lack of a module system. – jhufford Jan 24 '19 at 19:39

1 Answers1

1

Here is a (rather ugly) solution.

The core problem is you only need the size of A and B, which are constants, but you are forced to include the whole definition. The solution would be to manually calculate the size and write it in the required place.

But it is easy to forget to update the value when A and B are modified, so we should somehow automatically do the above job.

To achieve this, you can write a code generator, which generates code like this to a header file:

const size_t OverlayDataSize = /* the calculated size */;

and invoke that program each time you rebuild the whole project. (For example, by writing a Makefile. )

That generator can have A.hpp and B.hpp included, calculate max(sizeof(A), sizeof(B)) and run a printf or something similar to write the generated code. The other source files should only #include the generated source.

Since C++ does not have a module system (which will enable you to hide some internal entities) or a complete metaprogramming facility (which allow us to write some code that generates other code), I can only think of this rather ugly way to achieve this. But anyway, it should work.

Ruifeng Xie
  • 813
  • 4
  • 16
  • Ah clever! That does indeed achieve complete separation... I once used a similar idea of a generator program for version numbers on a project. – jhufford Jan 24 '19 at 08:13