Why are template mixins in C++ not more of a mainstay?
Asked Answered
Z

2

17

I use template mixins in C++ a lot, but I'm wondering why the technique isn't used more. It seems like the ultimate in reuse. This mix of power and efficiency is one of the reasons I really love C++ and can't see myself moving to a JIT language.

This article: http://www.thinkbottomup.com.au/site/blog/C%20%20_Mixins_-_Reuse_through_inheritance_is_good is a good backgrounder if you don't know what they are, and puts the case so clearly in terms of reuse and performance.

Zimmermann answered 6/1, 2012 at 8:57 Comment(9)
@GMan: I think the JIT reference is irrelevant to the question. It's a good question - it's an interesting design pattern which I've never seen in any of the code bases that I've worked on.Swansdown
@Skizz: It's totally irrelevant, I agree; but it's there.Albatross
+1: Never realized that this kind of technique can be used to implement mixins in C++ !Quincey
I don't know any way of doing this in a JIT language. I'm not sure if that relates to them being JIT languages.Zimmermann
Can any one suggest why people might think this should be closed?Zimmermann
@JessePepper: I guess they consider it a "borderline" question, as the answers could be fairly subjective ("I have seen it a lot" type ?). As for JIT: it's not really a matter of JIT, more a matter of dynamic vs static typing. Dynamic typing makes it easier (syntaxically) since all the methods you write are "templates" in a C++ sense. Of course you pay the cost with runtime errors...Caliginous
@MatthieuM not sure what you mean there. C++, C# and Java are all statically typed languages aren't they?Zimmermann
@JessePepper: Yes they are. I don't understand where you question comes from though, I (and your question) never mentionned either C# or Java...Caliginous
@MatthieuM: C# and Java are both JIT languages. I might be tempted to move to such a language if it had support for this kind of Mixin construct. There are probably too many other reasons why I wouldn't though, like RAII classes. Still, when I am forced to use C# or Java I would like to be able to use mixins.Zimmermann
C
24

The problem with mixins is... construction.

class Base1 { public: Base1(Dummy volatile&, int); };

class Base2 { public: Base2(Special const&, Special const&); };

And now, my super mixin:

template <typename T>
struct Mixin: T {};

Do you notice the issue here ? How the hell am I supposed to pass the arguments to the constructor of the base class ? What kind of constructor should Mixin propose ?

It's a hard problem, and it has not been solved until C++11 which enhanced the language to get perfect forwarding.

// std::foward is in <utility>

template <typename T>
struct Mixin: T {
  template <typename... Args>
  explicit Mixin(Args&&... args): T(std::forward<Args>(args...)) {}
};

Note: double checks are welcome

So now we can really use mixins... and just have to change people habits :)

Of course, whether we actually want to is a totally different subject.

One of the issues with mixins (that the poor article you reference happily skip over) is the dependency isolation you completely lose... and the fact that users of LoggingTask are then bound to write template methods. In very large code bases, more attention is given to dependencies than to performance, because dependencies burn human cycles while performance only burn CPU cycles... and those are usually cheaper.

Caliginous answered 6/1, 2012 at 9:35 Comment(27)
That overstates the practical problem a bit - can forward a small finite number of arguments with a little tedium (template <typename X> explicit Mixin(const X& x) : T(x) { } template <X, Y> explicit Mixin(X&, Y&) : T(x,y) { }...), or a large finite number with a bit of ugly preprocessor invocation....Bonin
@TonyDelroy: oh believe me people have tried. Given the combinations of const, volatile and & vs "value", it soon becomes untractable. Of course, for "one" particular case, it may work quite well, in general though it's difficult. And of course, as stated in the larger paragraph, while it's now technically feasible, it might still not be desirable for other reasons.Caliginous
I have thought of using a class trait to solve this issue to some degree (though never got round to trying it!). The idea is that each mixin declares a nested type called, say, ModelTraits which includes a T::ModelTraits as a member. Note that this is a recursive recursive definition. The ModelTraits is used to initialise the class and the T::ModelTraits can be passed to the base class constructor.Lastly
@Daniel: This is a nice solution, however it becomes somewhat difficult when the base class has multiple constructors.Caliginous
@Matthieu: except that in many code bases volatile's hardly ever used, and const T& vs. T is similarly typically irrelevant, so the multiplicative factor there is usually 2x for const and non-const. Again, tedium, but often practical :-(. The C++11 features are very welcome!Bonin
Well, the constructor parameter issue can be resolved using a two stage construction pattern, i.e. have an Initialise function that needs to be called after the object is constructed. The Symbian system does this (but that doesn't implement exceptions). It's not RAII, but that's the choice that needs to be made - RAII or Mixins - choose whatever is most appropriate.Swansdown
@Swansdown You just answered your own question. RAII is way more popular and considered rather useful.Messalina
@Matt - agreed on multiple constructors. In my experience, mixins tend to be very small chunks of reusable code and only a default constructor. On rare occasions I have need to initialise the state of the mixin (note that many mixins are stateless as I tend to store state in a "model" fed into the base of the chain); in this case I tend to call some "init" or "set" method on the mixin class from the final class's constructor (almost two-stage construction as noted by Skizz). In the end, object initialisation has not been enough of a problem to make not want to use mixins!Lastly
@DanielPaull: Ah Okay... I admit I tend to call stateless mixins "Policy" as popularized by Alexandrescu in Modern C++ Design.Caliginous
I think you could call it a "policy mixin" or "mixin policy". It certainly has the feel of policy based design but with the intention of chaining as seen in mixins. I think the blending of the idioms is very elegant and powerful.Lastly
Perhaps I should add a little more to my previous comment - the general approach of using multiple inheritance of policy classes allows a policy to be used only once within a class where as a mixin can appear multiple times in the mixin chain. I've used this fact many times (eg, to "mixin" a transform in a 3D drawing node which transform a part of the node - there is one transform mixin used per-part).Lastly
@DanielPaull: the policies may themselves be policed :)Caliginous
@Matthieu: why are users of LoggingTask bound to write template methods? The article included a mixin to add the polymorphic base class, doesn't that work?Shamus
@Matt - policies with that have policies still aren't mixins :)Lastly
@SteveJessop: there are two aspects here. First, in the article, MyTask no longer has a virtual method (too slow they say) in the "Mixin" section, so passing a LoggingTask<MyTask> to a foo(MyTask&) would not log. Second, a method wanting a LoggingTask& as parameter need be templated. Unless they use a shim (template adapter inheriting from non-template base class) to circumvent the issue (but then they no longer take a LoggingTask& as parameter)Caliginous
@Matthieu: They've offered that shim, though. If you want to avoid virtual calls, you write template functions. If you want to avoid template functions, you use TaskAdapter and pass it as an ITask&. They aren't claiming you can do both at once, but then neither does anybody else, and they do let you pick your preference. I agree about the constructors, I just don't think your additional criticism is justified. That said, there should probably be another polymorphic adapter, that takes the task it wraps by reference rather than by inheritance, to ease the boundary between the styles.Shamus
@Matt: (BTW - I wrote that article in case you didn't realise). The sole purpose of a Mixin is to help implement a concrete class. You will never, ever, ever find a Mixin in the wild; so there will never be a function that takes a LoggingTask& as a parameter. The way I use mixins is to inject common behaviour into a class that is implementing some pure-abstract interface. The only public interface to the instantiated object is this said pure-virtual interface. To bring it back to the article, MyTask would be declared and defined in a .cpp and a factory method exposed in a public header.Lastly
Btw I don't see why anyone would want, a priori, to take LoggingTask& as a parameter. By design it's a mixin, not an interface, so it's simply inappropriate to use it as a parameter type. [Edit - heh, I wrote that just as Daniel was writing the above. I guess he's not aiming to support the style with the template functions at all, although I'm pretty sure that in fact it is supported by the classes presented.]Shamus
@DanielPaull: I didn't realize. Therefore MyTask should inherit from ITask in the Mixin section as well should it not ?Caliginous
@Steve: Yeah, there is nothing to stop a mixin class (like those defined in the blog post) being passed to a template function, and if/when it is useful, I would certainly do it. I just haven't come across a need for it and it smells fishy to me given the purpose of mixins (as I understand it to be and as I use it in my code base), but in a wider context perhaps there are valid reasons for doing it - I'd love to hear arguments for cases where it might be useful.Lastly
@Matt: Whether MyTask inherits from ITask itself or whether you use another mixin to pull it in is probably just an implementation detail. I tend to favour coupling to frameworks as late as possible. The TaskAdapter may be a bit cheeky, but I like it - the coupling to the ITask interface becomes a concern of the factory rather than a concern of the MyTask class. Holy decoupling Batman.Lastly
@Matthieu: I wouldn't - if you don't want logging or timing, then the class you actually pass is an ITask& is TaskAdapter<MyTask>. Although, since TaskAdapter is just there to help you write your concrete class, you're perfectly entitled to ignore all the mixins and just write SomeTask that implements ITask. But that class would be concrete, you wouldn't normally add mixins to it, and if you're using any mixins at all then you might as well make TaskAdapter one of them and everything else non-virtual.Shamus
@Daniel: I think the "need" for everything to be template functions is tenuous. Really it's a question of whether you'd prefer to attach yourself to the concrete class at compile time (templates) or at link time (polymorphic interfaces). Usually the answer to that is "please rebuild as little as possible when I run make", but not always. It probably comes down to whether you like the idea of template public interfaces at all -- if you don't then it's fishy.Shamus
@Steve: Well written mixins tend to be very stable, so I can't say that using mixins vs polymorphic interfaces and build times necessarily correlate! Personally, I tend to favour generic programming until it is impossible - for example, when components are loaded from plugin DLLs or the design dictates that the user composes things at run time. Anyway, the fishy part was regarding functions that take mixin classes as arguments - I still find that idea odd.Lastly
@Daniel: Well, it would be something like template <typename Task> void do_task_twice(Task &task) { task.Execute(); task.Execute(); }. I don't see why that's any more fishy than void do_task_twice(ITask &task); unless you need to compose late.Shamus
@Steve: the funtion you noted - template <typename T> void do_task_twice( T& t ) - is fine to me as it is a genereic function that can take any type (yes, this includes mixin types, but the function writer did not write the function to take only some mixin type). To clarify, what I mean is that I don't like the idea of a function that takes a mixin type, such as - template <typename T> void do_task_twice( SomeMixinType<T>& t ). This was directly in response to Steve's gripe about foo(MyTask&) and "a method wanting a LoggingTask& as parameter" - both seem fishy.Lastly
GCC 5.1 was unable to compile this code until I changed std::forward<Args>(args...) to std::forward<Args>(args)... (note the end). Is that a typo in this answer?Irrespective
B
5

Templates require implementation to be visible in the translation unit, not just at link time (C++11 addresses that if you'll only use a pointer or reference to instantiations). This is a major issue for low-level code in enterprise environments: changes to the implementation will trigger (might or might not be automatically) massive numbers of libraries and clients to recompile, rather than just need relinking.

Also, each template instantiation creates a distinct type, which means functions intended to work on any of the template instantions have to be able to accept them - either themselves being forced to be templated, or they need a form of handover to runtime polymorphism (which is often easy enough to do: just need an abstract base class expressing the set of supported operations, and some "get me a accessor" function that returns a derived object with a pointer to the template instantiation and related entires in the virtual dispatch table).

Anyway, these issues are typically manageable, but the techniques to manage the coupling, dependencies and interfaces involved are a lot less publicised, understood and readily available than the simple mixin technique itself. Same is true of templates and policy class BTW.

Bonin answered 6/1, 2012 at 9:35 Comment(2)
I see the use of abstract interfaces as the primary way of avoiding coupling and thus compilation worries. I think it's a fair comment though. Our team is relatively small, but I do wonder if code bases need to necessarily be so huge and coupled.Zimmermann
If I had to crown a way "primary", I'd definitely go with plain old out-of-line implementation (sans virtual dispatch), but both pImpl and abstract interfaces have their place too. Anyway, these things can be managed. Another technique is to have a non-templated front-end for the specific template instantiation you want, with the template directly supporting the out-of-line implementation but not visible via the header. Lots of options to reduce or control coupling, as well as options for facilitating easy movement between runtime and compiletime polymorphism.Bonin

© 2022 - 2024 — McMap. All rights reserved.