Home > Net >  Is there a canonical way to handle explicit conversion between two externally-defined classes?
Is there a canonical way to handle explicit conversion between two externally-defined classes?

Time:05-29

I'm using two external libraries which define classes with identical contents (let's say Armadillo's Arma::vec and Eigen's Eigen::VectorXd). I would like to be able to convert between these classes as cleanly as possible.

If I had defined either class, it would be trivial to include a constructor or conversion operator in that class' definition, to allow me to write e.g.

Arma::vec foo(/*some constructor arguments*/);
Eigen::VectorXd bar = Eigen::VectorXd(foo);

but since both classes are from external libraries, I cannot do this. If I attemt to write a naive conversion function, e.g.

class A{
public:
  int value_;
  A(int value) : value_(value) {}
};

class B{
public:
  int value_;
  B(int value) : value_(value) {}
};

A A(const B& b){return A(b.value_);}

int main(void){
  A a(1);
  B b(2);
  a = A(b);
}

then the function shadows the class definition, and suddenly I can't use the A class at all.

I understand that allowing A a=b to be defined would be a bad idea, but I don't see why allowing A a=A(b) would cause any problems.

My question:

Is it possible to write a function or operator to allow the syntax A a=A(b)? And if not, is there a canonical way of doing this kind of conversion?

I've seen A a=toA(b) in a few libraries, but this isn't used consistently, and I dislike the inconsistency with the usual type conversions.

CodePudding user response:

Is it possible to write a function or operator to allow the syntax A a=A(b)?

No, it is not possible. The two classes involved define what conversions are possible and you can't change a class definition after it has been defined.

You will need to use a function as in your given example, although I would avoid repeating the type name and write

auto a = toA(b);

CodePudding user response:

TL;DR

Best engineering practice is to use design pattern Factory by introducing function (or utility class) that consumes Eigen::VectorXd and returns Arma::vec.

Arma::vec createFrom(Eigen::VectorXd from) { ... }

Any other hacking is a waste of time and introduction of tight coupling that will strike back sooner or later. Loose coupling is essential in SW engineering.

Detailed

You might introduce descendant of the target class where you would define a constructor like you described:

class MyArma : Arma::vec {
public:
    MyArma(Eigen::VectorXd from) : x(from.x), y(from.y), z(from.z) {
        /* empty constructor as we are fine with initializers */
    }
}

Then you'd just be able to create Arma vectors based on Eigen's vecotrs into E.g. Arma typed array

Arma::vec vecArray[] = { MyArma(eigenVect1), MyArma(eigenVect2) };

which comes from the principles of inheritance. Alternatively you could use a design pattern called Decorator where original vector (Eigen) is hidden behind the interface of the current vector (Armadillo). That involves overrding all the methods and there must be no public attribute and all the methods must have been delared as virtual... So lot of conditions.

However there are some engeneering flaws in above design. You are adding a performance overhead with Virtual Method Table, you are getting yourself in maintaining quite big and sensitive library for this purpose. And most important: You'd create technological dependency - so called spaghetti. One object shouldn't be avare about alternatives.

The documentation to armadillo gives nice hint that you should use design pattern called Factory. Factory is a standalone class or a function that combines knowledge of both implementations and contains algorihm to extract information from one and construct the other.

Based on http://arma.sourceforge.net/docs.html#imbue you'd best create a factory class that creates the target vector of the same size as the input vector and using method imbue(...) it would set the values of individual elements based on corresponding elements from the input vector.

class ArmaVecFacotry() {
   Arma::vec createFrom(Eigen::VectorXd from) {
      Arma::vec armaVec(from.size(), fill::none);
      int currentElement = 0;
      armaVec.imbue( [&]() { return from(currentElement  ); } );
      return armaVec;
   }
}

and then simply create objects like

Eigen::VectorXd sourceVector;
Arma::vec tergetvector = std::move(ArmaVecFactory::createFrom(sourceVector));

Notes:

  • You can have currentElement counter outside of the lambda expression as it is captured by [&]
  • I am creating the vector on stack but std::move outside make sure that the memory is being used effectively without excessive copying.
  • Related