6

I'm developing a C++ library where user's will provide complex inputs, such as matrices and quaternions. I don't want to have to reimplement these types so, internally, I'll be using the Eigen library.

I'm trying to decide on the best way to expose these types to my libraries' clients and have come up with a few options for my API. I use a quaternion type as an example, but this could apply equally to matrices and such. Also, although I'm specifically talking about exposing Eigen's types, I guess this question can apply equally well to other external libraries in use.

1) Use only basic C++ types

This option would require clients to pass data in via basic types. For example, for passing in a quaternion (4 elements), one could do:

void my_func(double my_quat[4])

2) Expose Eigen's Types

Eigen provides several templated types for arrays and quaternions. For example, if a function requires a quaternion, I could use Eigen's Quaterniond type (which is really a typedef for Quaternion<double>):

void my_func(const Eigen::Quaterniond& my_quat)

3) Create a simple wrapper for the various types for clients

I could create a very simple quaternion type (say, some sort of simple struct) that clients would have to create (perhaps via some sort of factory function) to pass to my API:

void my_func(const quaternion_t& my_quat)

My library would convert the quaternion_t type to my internal Eigen representation.

I don't like option 1 too much since I want there to be a stronger sense of typing in my APIs. Option 2 would require my clients to use Eigen as well, not to mention potential problems with compatibility should they use a different version of Eigen (incidentally, Eigen is a header-only library if that matters). That leaves option 3.

What do folks think? Have I basically answered my own question? Any examples out there?

Related Questions

A related question was asked here but didn't really go into details of whether one should expose external types.

Community
  • 1
  • 1
plasma
  • 848
  • 5
  • 9
  • What about option 3, with constructors that take both options 1 and 2? C++ semantics allow you to forward declare types well enough for this to work(clients without Eigen can still include the header and not fail at compile time). –  May 14 '12 at 01:55
  • I was thinking about something like that, but I guess I'm a little fuzzy on how to forward declare typedef'd template types, although I guess in my case I'll probably limit clients to passing a specific instantiation of the types (like `Quaternion` as opposed to `Quaternion`). – plasma May 14 '12 at 02:05
  • I'm also a little fuzzy about what would happen if a client uses the constructor to make my library's type from their Eigen type, but they are using a different version of Eigen that may, say, have a slight implementation change. – plasma May 14 '12 at 02:07

2 Answers2

3

Exposing the 3rd party libraries is the easiest in the short term, but will most likely to bite you in the back in the long term. Easiest, because the types are alrady there, you do not need to come up with your own. Will bite you if you would want to use a different implementation library in the future, or would want to allow expansion of the data the client passes to you.

Using only basic types is is almost like coming up with your own, but it's much lower level, for no good reason. Your users will have a hard time using your library without constantly refering to the documentation on what's what.

Using your own types is the best option if you want flexibility down the line. It might seem like a lot of work up front as you need to re-create all the already existing types, but if you give it some tought, you might find that if you use slightly different types in your library's interface, it will facilitate implementation change better later on.

So the answer really depends on your goals and long-term plans/predictions: if you don't see yourself ever changing from your current implementation, you can go with re-using the existing types, but if you foresee/plan change in the future, you should create your own independent interface.

Attila
  • 28,265
  • 3
  • 46
  • 55
2

Wrap / encapsulate. Say you want to add some additional feature, such as caching the result of a computation, like the norm of a quaternion, as an implementation change. You can't do that (as easily) if you expose the 3rd party types without forcing your client code to change its call.

djechlin
  • 59,258
  • 35
  • 162
  • 290
  • I agree. However, I don't necessarily want to expose an entire matrix/quaternion/etc. library from my own, since my library's functionality will be at a higher level. Would it make sense to for my wrapper classes to offer a minimum amount of functionality to communicate with my API, and let clients use whatever they want for actually manipulating the data associated with those types? – plasma May 14 '12 at 01:38
  • Thin wrappers are OK. my_quaternion::foo() { return their_quaternion.foo(); } is fine for the functions you want to expose. As @Attila pointed out above the most dramatic case is one in which you want to switch libraries, a hassle your client should not have to deal with. If you feel clients will need heavy-duty access to the full-fledged math objects from eigen: Also offer a conversion function getEigen() and a ctor that takes eigen as input to go to and fro that you will guarantee will always work even if you re-implement in another library. – djechlin May 14 '12 at 02:12
  • What would happen if the client using the ctor or getEigen() function and my library are compiled with different versions of Eigen? I assume that if both versions are ABI compatible, then we're good. But if something should change, would this not work anymore? In particular, I'm thinking of the case where I provide my library as a DSO, but I guess I don't know what would happen if I gave the clients a static library. – plasma May 14 '12 at 02:34
  • That question is over my head, but it might be a good idea to post it as a separate question. – djechlin May 14 '12 at 03:31