2

I'm writing a C (c99) library that operates on strings. I'm having a design problem writing functions that will perform in different modes.

For example; the Find function can search for:

  • the first, last, n-th element
  • different letter case
  • return position before or after the found element
  • search in a substring

Those modes cause exponential function naming. My solution is a function for each combination of modes:

Find(), FindCase(), FindSubstring(), FindSubstringCase(), FindSubstringCaseEnd(), FindEnd(), ...

and so on until every combination is made. I have written the find functions in a way where each mode is just a wrapper to a generic find function that is called internally to avoid duplication. This made adding new function modes easy, the only downside is the unusual function count.

I don't think any more modes will be added, so the current function count is within double digits. Still I'm unsure if that is the best solution.

Alternate solutions I considered and didn't find worthy are:

Having one generic function with a lot of parameters. This is inferior because it is easier to remember the function name than its parameters. It is also error prone and puts an unecessary burden on the user.

Have a function that takes a string of modes( like fopen() ). Same problems as above.

Variable arguments functions are just not worth considering.

I think my solution is decent, since the user only has to remember the name, naming is consistent, so you can "guess" the name, and cannot really make a mistake. In the worst case you have to look-up the name and the name could be up to ~20 characters, which still takes less space than additional parameters.

Also note that this problem applies to other functions( insert, erase, ... ) to a lesser degree.

Still, I'm looking for better solutions if there are any.

3
  • What about named parameters to specify how your find function should operate. For example, if you want to find from the end, you would call Find(FIND_END, ...); If you wanted to find from the end with case folding you would call Find(FIND_END|FIND_CASE, ...); etc.
    – Brandin
    Mar 22, 2015 at 12:51
  • @Brandin I consider it is the same as having separate functions except for the additional burden on the user.
    – reader
    Mar 22, 2015 at 13:51
  • @reader: The additional burden separate functions puts on the user is outlined in the answer I just posted.
    – Blrfl
    Mar 22, 2015 at 14:09

2 Answers 2

3

The amount of variables (and hence, the magnitude of the combinatorial explosion) can be reduced with a more general and modular API. For example, instead of making Find support substrings, create a separate function that takes a substring out of a string. Instead of returning either the start of the end of the match, always return the start and make it easy and cheap to compute the end from that.

If there is a generic implementation that all the variants would delegate to, you should probably expose it, if only to allow client code to call that function with run-time configuration (e.g., either ignore case or not depending on the value of a variable). Then you can still add convenience functions for common cases while culling the uncommon ones. printf is just fprintf with the first argument fixed to stdout.

1
  • Making a function that returns a substring reference to a string cut 85% of my functions. Thanks for that idea.
    – reader
    Mar 23, 2015 at 11:43
5

Using function names to differentiate among n true/false behaviors will result in 2n functions. This is easy to manage when n is a small value like 2 or 3, but it gets out of hand very quickly with anything greater. The POSIX open(2) call has nine true/false variants, and if that were written as one function per combination, you'd be staring down 512 functions to choose from.

Choose-by-name makes a lot of work for your function's callers. If my program has a dialog box with a text box for the user to enter a string and two check boxes to enable your "case" and "end" variants, my code ends up having to look like this:

char * string = get_pointer_to_string_typed_in_dialog_box();
_bool case_checked = dialogbox_case_checked();
_bool end_checked = dialogbox_end_checked();

if ( !case_checked && !end_checked ) { Find(string); }
else if ( case_checked && !end_checked ) { FindCase(string); }
else if ( !case_checked && end_checked ) { FindEnd(string); }
else if ( case_checked && end_checked ) { FindCaseEnd(string); }
else { assert(false);  // This shouldn't happen. }

Add a third check box and the size of that else if ladder doubles and adds one additional check to each condition. Multiply that by the number of times the function is called and you can pretty much see where that's headed.

One thing that's practically idiomatic in C (and the assembly language from which it descended) is the use of single-bit flags ORed together into an integer to enable one or more behaviors:

typedef size_t FindFlags;

#define FIND_CASE        ((FindFlags)1 << 0)
#define FIND_SUBSTRING   ((FindFlags)1 << 1)
#define FIND_END         ((FindFlags)1 << 2)
// Should do a static assertion to make sure the highest bit
// used isn't beyond what FindFlags can hold.

SomeReturnType find(char * string, FindFlags flags);

Someone wanting to call your function uses the same identifier every time and assembles the flags they need:

void find(some_string, FIND_END | FIND_CASE);

The hidden benefit in this is that it enables building up a set of flags programmatically without the else if monstrosity shown above:

char * string = get_pointer_to_string_typed_in_dialog_box();
FindFlags flags = 0;
if ( dialogbox_case_checked() ) { flags |= FIND_CASE; }
if ( dialogbox_end_checked()  ) { flags |= FIND_END; }
find(string, flags);

In the worst case you have to look-up the name...

That's a much worse case than you think it is. You'd have to establish a convention for how the combinations are strung together. Alphabetical order (FindCase(), FindCaseEnd(), FindCaseEndSubstring()) would be intuitive. If you're following the guideline that says you should write your code as if the people using it are psychotic and know where you live, forcing them to alphabetize their desired list of behaviors just to figure out the name of the function is a recipe for getting visits from axe-wielding developers.

... and the name could be up to ~20 characters, which still takes less space than additional parameters.

Unless you're planning to run this code in 1963, whether or not the developer has to key an additional dozen characters for a function call shouldn't even be on your radar. Make it readable and easy for your callers to use.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.