1

I have a design question around a problem I'm trying to solve, I am not sure if you would call this a "heterogeneous container". I want to be able to pass different container types (which share common properties) to functions, and define conversions between the types if the function requires a specific type.

The way I've thought to do this thus far is to have an abstract parent class HetroContainer, which specific types inherit from. The concrete type can be fetched by an enum defined in HetroContainer, where each enum element is a convenient label for the type of the child container.

Say I have two functions:

HetroContainer my_fun_a(HetroContainer input) {...}

HetroContainer my_fun_b(HetroContainer input) {...}

Both take and return HetroContainer. However, internally my_fun_a deals with let's say std::vectors, enum id VEC1, and my_fun_b deals with some other type, say Eigen tensors, or an std::vector but with a different element ordering, or whatnot, enum id VEC2.

In pseudocode:

my_fun_X

  1. Take generic container input
  2. Check to see if the input container's data is of my_fun_X's preferred type
  3. If it is not, then convert to that type (can/should be to new object)
  4. Run computations on container's data
  5. Return container

The parent class might look something like, where one would need to downcast to access the data:

enum TypeEnum{VECTOR_FMT1,
    VECTOR_FMT2,
          EIGEN};

class HetroContainer
{
public:
    HetroContainer(TypeEnum the_type,
                   int param1, int param2)
        :
        _the_type(the_type),
        _param1(param1), _param2(param2)
    {};

    TypeEnum the_type() const { return this->_the_type;};
    int param1() const { return this->_param1; };
    int param2() const { return this->_param2; };

    virtual void convert(HetroContainer input) = 0;
private:
    std::any _data;
    TypeEnum _the_type;
    int _param1;
    int _param2;
};

My program will deal with compositions of my_fun_a and my_fun_b, (and other my_fun_Xs). So if we have a composition my_fun_a(my_fun_a(input)), then in the first step of the first my_fun_a is to check if our HetroContainer is of the needed type VEC1.

The first my_fun_a() runs, and returns a HetroContainer, which is really a VEC1Container. Our second my_fun_a takes the HetroContainer, and checks that its underlying type is VEC1, so we don't need to bother converting.

In another case, my_fun_a(my_fun_b(input)), my_fun_a checks to see what the underlying type of its input is. It will find that it is not its required format (because my_fun_b() deals with the type VEC2. Thus, my_fun_a() must first call the relevant convert function (VEC2 -> VEC1) to convert the input to its required format. Once this conversion has finished, it continues running.

This conversion step will not necessarily be a simple cast, and will require manual implementation for each type-to-type. I would hope to be able to compose conversion, so that if I have a mapping from A->B and B->C, my program can infer that A->B->C can be performed. For these functions, I have been looking at.

I have been looking at QA forums about the web, but have been running into problems with downcasting. It feels like the overall design could be approached better, but I am not sure what I should be looking for. Any suggestions would be appreciated!

Have looked at:

Prunus Persica
  • 1,173
  • 9
  • 27
  • It would be nice to see a more concise (pseudo) code example what you actually want to achive, and how you want to use that. – πάντα ῥεῖ Feb 11 '19 at 17:31
  • thanks for the suggestion, my question is a bit verbose – Prunus Persica Feb 11 '19 at 17:38
  • That's not really what is pseudo-code in my opinion, but well. Should `HetroContainer` be implemented as template class? – πάντα ῥεῖ Feb 11 '19 at 17:40
  • Eventually, yes, but right now I would be satisfied without. I should also add that the underlying data might not lend itself well to standard iterator patterns, as it could be strange data types from outside libraries. – Prunus Persica Feb 11 '19 at 17:45
  • If you prefer OO design, which means run-time type checks, then inferring A->B->C from A->B and B->C is just the shortest path in a graph search. If you prefer generic template-based design and static compile-time checks, then it's the same thing, the shortest path in a graph... but now implemented at compile time. Lotsa fun. – n. m. could be an AI Feb 11 '19 at 17:50
  • n.m.: shortest path, but with the added fun that each edge could have varying cost, even for different cases of the same conversion (A->B could cost different amount depending on data size, and in some cases A->C->B could be cheaper!) – Prunus Persica Feb 11 '19 at 17:53
  • Yep, that's what "shortest" means. Cost=length. – n. m. could be an AI Feb 11 '19 at 18:05
  • How important is it that the container passed to a function is exactly the type expected? If you just template the functions on the container type (and expose a common container interface), will that work, or will some of the functions be impossible to implement, or unusably slow? – Useless Feb 11 '19 at 18:08
  • Useless: it's a property of the functions that they can only use a specific type, but the types are related enough that conversion is possible. I'm not sure if that's what you mean – Prunus Persica Feb 11 '19 at 18:42

0 Answers0