Modern Maintainable Code

Code reuse series: When half is the same and half is different

| Comments


In the last article we talked about how to have multiple fundamental implementations for a single conceptual struct. Essentially, if we wanted to change the behavior of a struct depending on what types you instantiate it with; if I wanted to create a special version of vector that was memory optimized when it holds bools, I can intelligently partially specialize the case vector<bool> to behave differently than the "normal" or "default" implementation for other types.

When you combine that with what we've learned about tag dispatch in the series as it applies to structs, we end up with a summary that looks like this:

Want to have different behavior in a struct for different types? If so, do you want to change all of the internals? If so, use partial specialization. Only want to change a small portion, maybe a member function or two? Use tag dispatch on member functions or abstract the functionality into a partially specialized class that lives in the main one as a data member (like std::unique_ptr's deleter).

Thus far we've seen how to handle the extremes quite well. If you want to change almost all of the things or almost none of the things, it's pretty simple: just apply one technique and you're done. Things get messy when you want to do something in the middle, when you have lots of functions and a bunch of them need to be implemented the same way for all type instantiations and a bunch need to be different, and that's what we're going to talk about next.

Questions for next time:

1) Why is "normal" partial specialization undesirable when we want half of our functions to be implemented the same way when our struct is instantiated with different types?

2) Why is tag dispatch on member functions undesirable when we want half of our functions to be implemented the same way when our struct is instantiated with different types?

3) What object-specific technique allows us to write code once and reuse it many times?

4) How can we use the technique from question 3 to solve the problem of having an object for which we want half the methods to depend on what types its instantiated with and half to remain the same? (Hint: combine with one of the techniques we've talked about earlier in the series).

For bonus points, there are actually 2 ways to apply the same techniques to answer question 4 (though I think most readers will have trouble coming up with the way I think is better).


By this point in the series, you should be able to answer these fundamental code reuse questions:
1) How do I reuse the same implementation code with different types? (Review here)
2) How do I use the same code to invoke different fundamental implementations selected by type? (Review here)
3) How do I reuse code for some types but select different fundamental implementations for others? What if there are patterns to the "exceptions" to the "default code"? (Review here for functions, and review here for structs)

If you struggle with these concepts, I highly recommend going back and (re)reading the relevant articles, then playing around with the concepts in your own code until they start to click and make sense.


comments powered by Disqus