1

I'm engineering library to provide functionality for one device. This device has some common operations but different algorithms to accomplish these operations. I want one function prototype for one particular operation, not a bunch of them, say instead of:

Alg1_Foo();
Alg2_Foo();
...

I want this:

Foo(alg);

But I don't want to pass alg as separate argument, because functions will have a lot of arguments even without it, they will have arguments for identification and/or authorization of the device, in argument, out argument (at least one of them), so I think it will be annoying to add alg as a separate argument.

So my idea is to provide solution like this one:

Foo(const SomeUnion& some_union);

Where:

union SomeUnion {
  AlgId alg_id;
  alg1::SomeStruct alg1_some_struct;
  alg2::SomeStruct alg2_some_struct;

  SomeUnion(alg1::SomeStruct some_struct) { alg1_some_struct = some_struct; };
  SomeUnion(alg2::SomeStruct some_struct) { alg2_some_struct = some_struct; };
};

And the structures of particular algorithms will be like this:

namespace alg1 {
  struct SomeStruct {
    static const AlgId alg_id = ALG1;
    . . .
  };
}

So if want to perform say alg1, we pass appropriate structure to Foo and it works in C++, say

alg1::SomeStruct a;
Foo(a);

But I want my library to maintain possibilities of pure C. Of course I need to:

  1. remove references and replace them with pointers;

  2. remove namespaces (we can still emulate them with the help of structures (this thread may be helpful for those interested: Namespaces in C);

  3. replace C++-style of defining structs to that from C and define also name in tag namespace (typedef struct tagStruct {...} Struct;);

  4. remove functions from inside structures and unions.

But though I can't understand if it's possible to accomplish what I want to do with maintenance of C... Do you see the way? Or is it just simplier to pass alg_id as a separate argument not to bother with unions and structures (but I want to avoid it if possible)?

Community
  • 1
  • 1
greenpiece
  • 621
  • 8
  • 20
  • So why don't you want separate functions for each algorithm? – Thomas Mar 01 '13 at 23:26
  • What is the question? What problem are you trying to solve? – Peter Wood Mar 01 '13 at 23:29
  • 2
    overengineered comes to mind. – Tony The Lion Mar 01 '13 at 23:31
  • 1
    Mayhap it's overengineered but we have the library which was engineered by the person who worked in the company before me and we have a lot of prototypes for each separate algorithm. I won't say it's easy to maintain them, say, if logic of particular operation changes, and we need to change all the prototypes connected with it. It's just a proposal what was written above, if it's impossible to accomplish in C I will accept it and leave separate functions or add alg_id as argument. But if you have some clues they will be appreciated. – greenpiece Mar 01 '13 at 23:43
  • With C++ you can overload `Foo` to take a different struct for each algorithm. So you're really defining a bunch of functions (which you say you don't want to do, but don't given any reason why), they just all have the same name. – Chris Dodd Mar 02 '13 at 02:40
  • See my comment to the answer of Mats Petersson, I give my motivation there. Overloading of functions won't do because though I use C++ I want this library to work with C applications also. – greenpiece Mar 02 '13 at 10:02

1 Answers1

3

To me, this seems like a typical case where someone is trying to solve a problem "the wrong way".

If you have "lots of arguments" to a function, then using a struct is fine. But hiding "which function you are actually calling" inside such a struct, and then having several variants ofstruct inside a union, now we're trying to stuff too much into one function. Start to split the function so that it only takes one struct - remove your union as an argument - that's just wrong. [I have worked with code that does similar things to this - the chances of sneaking in some code that does the wrong thing and causes some hard to find error because you passed the wrong type of union arguments into the function makes this really a poor solution]. If you pass the wrong type of structure to a plain functions then the compiler tells you. If you fill in the wrong part of a union, the code using the "right" part of the structure will get "weird" data [quite possibly undefined behaviour] - this type of bug can be darn difficult to find.

So yes, split your functions, remove the union!

Edit: One suggestion that comes to mind if you wish to have a simple and consistent interface is that you have a factory function, which you pass alg_id to, which returns a function pointer to the relevant function. Unfortunately, if the interface to each function is as I suggest, a different structure, then you still have a potential for getting the data structures and functions mixed up, but it does cut down on the number of "published functions" - in fact, those functions don't need to be visible at all outside of the module [library or even object file] that provides them.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Thank you for detailed answer. I understand drawbacks of this... My idea was to: 1) make it possible not to change interface of the library (keep function names the same) when adding new algorithm (so only structures will change); 2) prevent user from using the wrong structure when he or she wants to use certain algorithm, so I decided to hardcode alg_id inside of every structure. Point 2 is easy achievable with split functions but point 1 is not. There will be some layer calling the functions of library and it won't be so easy to change its interfaces every time. Or is it a necessary extent? – greenpiece Mar 02 '13 at 09:57
  • If you are changing algorithms, then you probably introduce new data structures [if I understand your problem correctly], which means new header files - so introducing a new function at the same time shouldn't be a problem unless you get a name collision. Using names that are unlikely to clash with your client code's names would help here, e.g using a prefix and then documenting that "client functions should not start with `PfX_` or whatever your prefix is. The interface library undoubtedly also needs to change to support new algorithms. (more...) – Mats Petersson Mar 02 '13 at 10:01
  • Putting the `alg_id` in the `struct` doesn't help unless you make sure 1. The `alg_id` isn't in the same place for any two structs. 2. None of the data used could be confused with the value `alg_id`. Both of these will become harder and harder to achieve the more functions you add. See also edit above... – Mats Petersson Mar 02 '13 at 10:03