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
?
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.