I'm trying to design a class - let's call it A
for the sake of discussion - that will fulfill a specific set of requirements:
A
must be a literal type to allow the compiler to initialize its global instances at compile time via constexpr
constructors (many global const
objects of this type are created in the source code). The reason for this is that A
employs simple compile-time encryption (mostly XOR) on the integers. Decryption is later done at runtime while accessing the appropriate int
.A::A(int x)
and A::A(int x, int y, int z)
. If the first version is invoked, then later, at runtime, that single x
will be used by the class internally whenever a method call that needs to use it is made. In contrast, if the second version with three parameters is used, it will be decided at runtime which one of x
, y
and z
to use if a function that needs one is invoked.A
must be a single type; one type for the first constructor and another type for the second constructor is not satisfactory (A
is frequently passed to functions as an argument, so without this constraint, I would need to duplicate or templatize all those functions).A
objects will be global constants, assignment will seldom happens, and if it does, it will certainly not be between an object with three int
s and an object with one int
or vice-versa.To summarize: A
will be instantiated as global const
objects. If I initialize an object with a single int
, that single int
should be stored inside it (and nothing more). If I initialize it with three int
s, then those three int
s should be stored inside. There's no need to worry about assignment from a three-int
object to a one-int
object or vice versa, because they're all constants.
The solutions I have considered so far are as follows:
Make A
a template; the template parameter would be what I called the StorageType
. That storage would abstract access to that central int
resource by exposing an accessor for it. The problem of choosing which int
field to use would then be moved from the A
to this helper storage type. The idea is roughly illustrated by this code:
template<typename StorageType>
class A
{
private:
StorageType storage;
public:
constexpr A(int x, int y, int z) :
storage(x, y, z)
{ }
constexpr A(int x) :
storage(x)
{ }
void doSomething()
{
auto theRightInt = storage.getInt();
// ...
}
};
Problem: violates constraint 3.
As before, but rather than templatize A
on the StorageType
, have a generic interface that describes the StorageType
and store a unique_ptr
to one inside A
.
Problem: violates constraint 1.
Store the integers as a union:
union
{
struct
{
int x;
int y;
int z;
} intPack;
int singleInt;
};
In this variant, each A
object - including those that only use a single int
- has room for three possible int
s. Another option would be to use boost::variant
instead of the obsolete union
.
Problem: This is wasteful - see point 4. If boost::variant
is used, it violates constraint 1 since boost::variant
, unlike std::variant
from C++17, is not a literal type.
Rather than attempt to represent the "variance" of the central integer field inside A
, do it externally: have the class always store a single int
, and create a helper class - let's call it VariantA
- that contains three versions of A
, each initialized with what was x
, y
and z
in the constructor mentioned in point 2. VariantA
would have an accessor function that decides at runtime which A
object to return.
Problem: Tedious to use because the accessor has to be invoked every single time:
VariantA a1(0, 1, 2);
VariantA a2(10, 20, 30);
auto a3 = a1.get() + a2.get();
someFunctionThatTakesAnA(a1.get());
// etc
Question: Is there an elegant solution to this problem that I have missed, or will I have to choose one of the solutions I have considered (and rejected) above? My platform is VC++, so use of C++11/4 is okay except some more esoteric parts, but the features from C++17 drafts are not yet available.
Seems like the best thing you could use is a conditionally sized range of ints:
class A {
std::array<int, 3> data;
bool one;
public:
constexpr A(int x): data{{x, 0, 0}}, one(true) { }
constexpr A(int x, int y, int z): data{{x, y, z}}, one(false) { }
constexpr size_t size() const { return one ? 1 : 3; }
constexpr const int* begin() const { return data.data(); }
constexpr const int* end() const { return data.data() + size(); }
};
I'm not entirely sure what your logic is for selecting which element, but if you have a 1-sized range, you get that element, if you have a 3-sized range, then you do whatever it is you need to do.
Regardless of the specific details, the main point is that you want a type that has 3 int
s and a bool
. You need to store 3 int
s, you want it to be literal, and you want it to know whether it's storing 3 or 1 int
. That pretty much spells out the type. The only thing left is to determine how you want the data access to look, and that's not specified.