Search code examples
c++c++11class-design

Designing a literal-type class with a variant field inside in which one or three objects may be stored


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:

  1. 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.
  2. Its central private field is a simple integer. However, the class has two constructors: 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.
  3. 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).
  4. A great majority of all A objects will be global constants, assignment will seldom happens, and if it does, it will certainly not be between an object with three ints 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 ints, then those three ints 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 ints. 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.


Solution

  • 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 ints and a bool. You need to store 3 ints, 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.